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 deleted file mode 100644 index 96c27495ebb..00000000000 --- a/.gitignore +++ /dev/null @@ -1,167 +0,0 @@ -################# -## 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 00000000000..49c919e51d3 --- /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 'main' 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..b5dec9bf96d --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,217 @@ +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/ + '!vendor/**', // Exclude vendor/ + '!tests/**', // Exclude tests/ + '!includes/blocks/node_modules/**', // Exclude includes/blocks/node_modules/ + '!includes/libraries/**', // Exclude includes/libraries/ + ], + expand: true, + }, + }, + + // Clean up build directory + clean: { + main: [ 'build/**' ], + repo: [ 'build/<%= pkg.name %>-public/**' ], + }, + + // Copy the plugin into the build directory + copy: { + pro: { + src: [ + 'assets/**', + '!assets/lite/**', + 'i18n/**', + 'includes/**', + '!includes/blocks/node_modules/**', + '!includes/blocks/composer.json', + '!includes/blocks/package.json', + '!includes/blocks/package-lock.json', + 'languages/**', + 'libraries/**', + 'templates/**', + 'src/**', + '!src/Lite/**', + '*.php', + '*.txt', + '!vendor/**', + 'vendor/autoload.php', + 'vendor/composer/**', + 'vendor/symfony/deprecation-contracts/**', + 'vendor/symfony/polyfill-php80/**', + 'vendor/symfony/polyfill-mbstring/**', + ], + dest: 'build/<%= pkg.name %>-pro/', + }, + lite: { + src: [ + 'assets/**', + '!assets/pro/**', + 'i18n/**', + 'includes/**', + '!includes/blocks/pro/**', + '!includes/blocks/assets/pro/**', + '!includes/blocks/build/pro/**', + '!includes/blocks/node_modules/**', + '!includes/blocks/src/pro/**', + '!includes/blocks/composer.json', + '!includes/blocks/package.json', + '!includes/blocks/package-lock.json', + 'languages/**', + 'libraries/**', + 'templates/**', + 'src/**', + '*.php', + '*.txt', + '!src/Pro/**', + '!vendor/**', + 'vendor/autoload.php', + 'vendor/composer/**', + 'vendor/symfony/deprecation-contracts/**', + 'vendor/symfony/polyfill-php80/**', + 'vendor/symfony/polyfill-mbstring/**', + ], + dest: 'build/<%= pkg.name %>/', + }, + repo: { + src: [ + '**', + 'assets/**', + '!assets/pro/**', + '!build/**', + 'i18n/**', + 'includes/**', + '!includes/blocks/pro/**', + '!includes/blocks/assets/pro/**', + '!includes/blocks/build/pro/**', + '!includes/blocks/node_modules/**', + '!includes/blocks/src/pro/**', + 'languages/**', + 'libraries/**', + '!node_modules/**', + 'templates/**', + 'src/**', + '!src/Pro/**', + '!vendor/**', + 'vendor/autoload.php', + 'vendor/composer/**', + 'vendor/symfony/deprecation-contracts/**', + 'vendor/symfony/polyfill-php80/**', + 'vendor/symfony/polyfill-mbstring/**', + '!crowdin.yml', + '!sonar-project.properties', + ], + dest: 'build/<%= pkg.name %>-public/', + } + }, + + // 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: { + 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' + } + ] + }, + repo: { + 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-public/easy-digital-downloads.php' ], + dest: 'build/easy-digital-downloads-public' + } + ] + } + } + } ); + + // Build task(s). + grunt.registerTask( 'prep', [ 'clean', 'force:checktextdomain' ] ); + grunt.registerTask( 'build', [ 'clean', 'pro', 'lite' ] ); + grunt.registerTask( 'pro', [ 'copy:pro', 'compress:pro' ] ); + grunt.registerTask( 'lite', [ 'copy:lite', 'replace:pro' ] ); + grunt.registerTask( 'repo', [ 'clean:repo', 'copy:repo', 'replace:repo' ] ); +}; 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..62cd73bff62 --- /dev/null +++ b/assets/css/admin/chosen/_base.scss @@ -0,0 +1,50 @@ +.chosen-container { + font-size: 14px; + + .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; + } + + .spinner { + margin: 0; + position: absolute; + top: 4px; + right: 30px; + } +} + +.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; +} 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..f0cf3eda8c0 --- /dev/null +++ b/assets/css/admin/chosen/_multi.scss @@ -0,0 +1,61 @@ +.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 24px 3px 10px; + width: 100% !important; + } + + .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/dashboard/_widget.scss b/assets/css/admin/dashboard/_widget.scss new file mode 100644 index 00000000000..eac4d7836c8 --- /dev/null +++ b/assets/css/admin/dashboard/_widget.scss @@ -0,0 +1,110 @@ +.edd_dashboard_widget { + display: grid; + grid-template-columns: repeat(2, minmax(150px, 1fr)); + grid-gap: 1em; + + > div:not(.table_left):not(.table_right) { + grid-column: span 2; + } + + table thead td { + border-bottom: 1px solid $wp-border; + color: #777; + } + + .inside { + font-size: 12px; + } + + td { + padding: 3px 0; + } + + .b, + .t { + line-height: 1.5; + vertical-align: middle; + } + + .b { + text-align: right; + } + + .t { + font-size: 12px; + padding-right: 12px; + color: #777; + width: 100%; + } + + .label_heading { + border-top: 1px solid $wp-border; + color: #8f8f8f; + font-size: 12px; + font-weight: normal; + display: block; + padding-top: 10px; + margin: 0 0 8px 12px; + } + + .edd_dashboard_widget_subheading { + border-top: 1px solid $wp-border; + color: #8f8f8f; + font-size: 14px; + padding-top: 10px; + margin: 1em 0 0 0; + } + + .edd_dashboard_widget_subheading+.table { + margin: 8px 0 0 0; + } + + .edd_price_label { + background: var(--wp-admin-theme-color); + border-radius: 3px; + color: white; + font-size: 10px; + padding: 2px 4px; + margin-right: 2px; + } + + 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; + } +} + +.edd-dashboard-notice { + grid-column: span 2; + padding: 1px; + text-align: center; + margin: 1em -1em -1em; + background-color: $wp-alternate; + border: 1px solid $wp-border; + + &--error { + background: $wp-red-50; + color: $white; + + a { + color: $white; + } + } +} diff --git a/assets/css/admin/dashboard/style.scss b/assets/css/admin/dashboard/style.scss new file mode 100644 index 00000000000..691d8726d51 --- /dev/null +++ b/assets/css/admin/dashboard/style.scss @@ -0,0 +1,5 @@ +@import "widget"; + +body.dashboard_page_edd-upgrades.js .postbox .hndle { + cursor: default; +} 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/discounts/_add-edit.scss b/assets/css/admin/discounts/_add-edit.scss new file mode 100644 index 00000000000..58e36c2b54a --- /dev/null +++ b/assets/css/admin/discounts/_add-edit.scss @@ -0,0 +1,108 @@ + +#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-code-wrapper { + display: flex; + align-items: stretch; + gap: 3px; +} + +.edd-popup-trigger { + display: flex !important; + align-items: center; + gap: 3px; + + @media screen and (max-width: 782px) { + margin-bottom: 0 !important; + } + + @media screen and (max-width: 480px) { + span:not(.dashicons) { + display: none; + } + } +} + +.edd-code-generator-popup { + @include edd-popup( $width: 250px, $transformX: 240px, $transformY: 15px, $pointer-size: 15px, $pointer-position: top ); + + @media screen and (max-width: 480px) { + transform: translateY(15px) translateX(105px); + + &:after { + left: 70%; + } + } + + .edd-form-group { + width: 100%; + margin-bottom: 10px; + padding-bottom: 10px; + box-sizing: border-box; + margin-top: 0; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid $wp-gray-5; + height: 40px; + + &:last-of-type { + border-bottom: 0; + } + + label { + padding: 5px 0; + width: 60px; + font-size: 12px; + margin-bottom: 0; + box-sizing: border-box; + + @media screen and (max-width: 782px) { + line-height: 28px; + } + } + + input:not([type="checkbox"]):not([type="radio"]) { + width: 120px !important; + min-height: 0; + height: 30px; + + &:not(:focus) { + border: 1px solid $wp-input-border-2; + } + } + } + + #edd-generate-code { + width: 100%; + + @media screen and (max-width: 782px) { + &:before { + margin-top: 8px; + } + } + } +} diff --git a/assets/css/admin/discounts/_list-table.scss b/assets/css/admin/discounts/_list-table.scss new file mode 100644 index 00000000000..b8af3faba87 --- /dev/null +++ b/assets/css/admin/discounts/_list-table.scss @@ -0,0 +1,9 @@ +.wp-list-table.discounts { + .column-amount { + width: 90px; + } + + th.column-use_count { + width: 150px; + } +} diff --git a/assets/css/admin/discounts/style.scss b/assets/css/admin/discounts/style.scss new file mode 100644 index 00000000000..54245f44010 --- /dev/null +++ b/assets/css/admin/discounts/style.scss @@ -0,0 +1,3 @@ +@import "../../variables/colors"; +@import "list-table"; +@import "add-edit"; diff --git a/assets/css/admin/downloads/_actions.scss b/assets/css/admin/downloads/_actions.scss new file mode 100644 index 00000000000..5633baf9f4f --- /dev/null +++ b/assets/css/admin/downloads/_actions.scss @@ -0,0 +1,18 @@ +.edd__handle-actions-order { + display: flex; + transition: opacity 0.25s ease-in-out; + + button { + padding: 9px; + border: 0; + background: transparent; + + &:disabled .dashicons { + opacity: 0.5; + } + } + + &.edd-hidden { + display: none; + } +} 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..f690ecbc983 --- /dev/null +++ b/assets/css/admin/downloads/_prices.scss @@ -0,0 +1,124 @@ +.edd-variable-prices__wrapper { + display: flex; + flex-direction: column; +} + +.edd__header-footer { + display: flex; + flex-direction: row; + justify-content: flex-start; + gap: .5em; + padding: 16px 0; + + .button-link { + text-decoration: none; + } + + .edd-header-separator { + display: block; + } +} + +.edd-variable-prices__rows { + border-top: 1px solid $wp-border; +} + +.edd-variable-price__row { + padding: 20px; + border: 0; + border-radius: 0; + border-bottom: 1px solid $wp-border; + display: flex; + flex-wrap: wrap; + gap: 16px; + + .edd-section-content__fields--standard { + align-items: center; + flex-grow: 1; + border: 0; + + @media screen and (min-width: $break-mobile) { + flex-wrap: nowrap; + } + + .regular-text { + width: 100%; + max-width: 100%; + } + + .edd-form-group__control { + margin-bottom: 0px; + } + + .edd-option-price { + margin-left: auto; + } + } + + .edd-custom-price-option { + &-section { + display: block; + padding: 20px; + padding-left: calc(2rem + 12px); + border-top: 1px solid $wp-border; + + &-title { + display: block; + font-weight: 600; + padding: 0 0 10px; + } + + &-content { + display: flex; + gap: 12px; + } + } + } + + .toggle-custom-price-option-section { + color: $wp-gray-40; + + &:hover { + color: $wp-text; + } + } + + .edd-variable-prices__default { + margin-left: calc(2rem + 12px); + margin-right: auto; + + input:checked { + opacity: .5; + } + } + + .edd-section__actions { + align-self: flex-end; + margin-left: auto; + display: flex; + gap: 8px; + } +} + +.edd-variable-price__id { + flex-basis: 2rem; + font-weight: 600; + margin-bottom: 0; + text-align: right; +} + +.edd-variable-price__name { + flex-grow: 1; +} + +.edd-section-content__fields--custom { + flex-basis: 100%; + + .closed & { + display: none; + } + + .open & { + display: grid; + } +} diff --git a/assets/css/admin/downloads/_product-files.scss b/assets/css/admin/downloads/_product-files.scss new file mode 100644 index 00000000000..d68f251f46a --- /dev/null +++ b/assets/css/admin/downloads/_product-files.scss @@ -0,0 +1,31 @@ +#edd_product_files { + &.ajax--loading { + position: relative; + + &:before { + background: none; + display: block; + position: absolute; + top: 50%; + left: 50%; + z-index: 5; + @include edd-spinner( + 1.25em, + $wp-input-border, + $wp-alternate + ); + } + + &::after { + background-color: rgba( $white, .75 ); + display: block; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + content: ' '; + z-index: 1; + } + } +} diff --git a/assets/css/admin/downloads/_repeatable_row.scss b/assets/css/admin/downloads/_repeatable_row.scss new file mode 100644 index 00000000000..6a5a97f13c7 --- /dev/null +++ b/assets/css/admin/downloads/_repeatable_row.scss @@ -0,0 +1,191 @@ +.edd-repeatables-wrap { + display: flex; + flex-direction: column; + gap: 16px; +} + +.edd_repeatable_row { + border: 1px solid $wp-border; + border-radius: 3px; + margin: 0; + padding: 0; + + &.ui-sortable-placeholder { + line-height: 0; + padding: 0; + margin: 0; + box-sizing: border-box; + border: 1px dashed $wp-border; + visibility: visible !important; + } + + & input[type="text"].large-text { + width: 100%; + } + + & .edd_repeatable_row.ui-sortable-helper { + & .edd-repeatable-row-actions { + & .edd-remove-row { + display: none; + } + } + } +} + +.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-actions { + color: $wp-gray-40; + + a { + text-decoration: none; + width: auto; + cursor: pointer; + } +} + +.edd-repeatable-row-header { + clear: both; + background: $wp-gray-0; + border-bottom: 1px solid $wp-border; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 8px; +} + +.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-bundle-products-header { + margin-top: 0; +} + +.edd-repeatable-row-title { + font-weight: 600; + padding: 9px 0; + margin-right: auto; +} + +.edd-repeatable-row-actions { + display: flex; + margin-left: auto; + align-items: center; + gap: 8px; +} + +.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; + display: flex; + justify-content: space-between; + align-items: center; + gap: 18px; + + .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-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/_sections.scss b/assets/css/admin/downloads/_sections.scss new file mode 100644 index 00000000000..dc11716cd3e --- /dev/null +++ b/assets/css/admin/downloads/_sections.scss @@ -0,0 +1,28 @@ +.edd-download-editor__sections { + margin-top: 20px; + + &.edd-sections-wrap { + border: 1px solid $wp-border; + } + + .section-wrap .edd-section-content__fields--standard .edd-form-group { + margin-bottom: 16px; + } + + .section-content .inside { + margin: 0 !important; + padding: 0; + } +} + +body:not(.block-editor-page) { + #edd_product_details .inside { + margin: 0; + padding: 0; + } + + .edd-download-editor__sections { + border: none; + margin-top: 0; + } +} diff --git a/assets/css/admin/downloads/_settings.scss b/assets/css/admin/downloads/_settings.scss new file mode 100644 index 00000000000..39bfb1033ad --- /dev/null +++ b/assets/css/admin/downloads/_settings.scss @@ -0,0 +1,6 @@ +.edd-buy-buttons { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 20px; +} 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..cdfa0a2ef69 --- /dev/null +++ b/assets/css/admin/downloads/style.scss @@ -0,0 +1,9 @@ +@import "../variables/variables"; +@import "repeatable_row"; +@import "prices"; +@import "upload"; +@import "duplicate"; +@import "product-files"; +@import "sections"; +@import "settings"; +@import "actions"; 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/emails/_editor.scss b/assets/css/admin/emails/_editor.scss new file mode 100644 index 00000000000..610549a7627 --- /dev/null +++ b/assets/css/admin/emails/_editor.scss @@ -0,0 +1,67 @@ +.edd-email-editor__description { + margin: 2em 0; +} + +.edd-editor__header--actions { + position: relative; + + .edd-email-status-badge { + position: absolute; + right: 0; + top: 53px; + margin-right: 20px; + border: 1px solid; + padding: 12px; + } + + #submit.edd-updating { + &:before { + background: none; + @include edd-spinner( + 12px, + $wp-alternate, + $wp-input-border + ); + } + } +} + +.edd-form__email { + background: $white; + border: 1px solid #e2e4e7; + padding: 2em; + max-width: $break-huge; + + @media screen and (min-width: $break-medium) { + $first-column-width: 200px; + .edd-form-group { + display: grid; + grid-template-columns: $first-column-width 1fr; + margin-bottom: 2em; + + > .description { + grid-column-start: 1; + grid-column-end: 3; + } + } + + .edd-form-group > .description, + .notice { + margin-left: $first-column-width; + } + } +} + +.edd-email-action-reset { + border-color: $edd-alert-red !important; + color: $edd-alert-red !important; + + &:hover { + border-color: $edd-alert-red-hover !important; + color: $edd-alert-red-hover !important; + } +} + +.edd-editor__actions--test + .notice { + margin-top: 2em; +} diff --git a/assets/css/admin/emails/_logs.scss b/assets/css/admin/emails/_logs.scss new file mode 100644 index 00000000000..655292cea90 --- /dev/null +++ b/assets/css/admin/emails/_logs.scss @@ -0,0 +1,6 @@ +#edd-email-logs { + #edd-filters { + padding-left: 0; + padding-right: 0; + } +} diff --git a/assets/css/admin/emails/_promo.scss b/assets/css/admin/emails/_promo.scss new file mode 100644 index 00000000000..0dace34ee1b --- /dev/null +++ b/assets/css/admin/emails/_promo.scss @@ -0,0 +1,76 @@ +.edd-emails__add-new__overlay { + position: absolute; + right: 0; + top: 40px; + background: white; + padding: 8px; + border: 1px solid $wp-border; + border-radius: 4px; + display: flex; + flex-direction: column; + gap: 2px; + z-index: 1; + align-items: stretch; + + button.button { + background: none !important; + border: none; + color: $wp-text !important; + text-align: left; + } +} + +#edd-admin-notice-emails { + max-width: 100%; + width: 800px; + text-align: left; + padding: 2em; + + h2 { + line-height: unset; + margin: 0; + } + + .edd-extension-manager__body { + grid-template-rows: unset; + margin-bottom: 1.5em; + } + + .edd-promo-notice__ajax & { + width: 400px; + + .edd-extension-manager__body { + grid-template-columns: 80px 1fr; + } + } + + .edd-extension-manager__card-group { + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + text-align: left; + margin: 0 auto 1em; + width: 100%; + + .edd-extension-manager__single-card { + margin: 0 auto 1em; + } + } + + .edd-promo-notice__overlay:not(.edd-promo-notice__ajax) & { + .edd-extension-manager__body { + grid-template-columns: 60px 1fr; + } + + .edd-extension-manager__icon { + width: 58px; + height: 58px; + + img { + width: 40px; + } + } + } + + .edd-plugin__recommended { + display: none; + } +} diff --git a/assets/css/admin/emails/_table.scss b/assets/css/admin/emails/_table.scss new file mode 100644 index 00000000000..482ceb0c03c --- /dev/null +++ b/assets/css/admin/emails/_table.scss @@ -0,0 +1,95 @@ + +#edd-emails__add { + display: flex; + align-items: center; + gap: 8px; + justify-content: space-between; +} + +.edd-list-table__item td { + padding: 10px; +} + +th.column-status { + text-align: right; + width: 80px; +} + +td.column-status { + min-height: 30px; + text-align: right; + width: 80px; + + & .edd-help-tip { + margin-top: 2px; + } + + @media screen and (max-width: $break-medium) { + text-align: left; + width: unset; + min-height: 40px; + } +} + +tr.no-items:not(:only-child) { + display: none; +} + +@media screen and (min-width: $break-wide) { + .column-primary { + width: 250px; + } + + .column-recipient, + .column-sender { + width: 125px; + } + + .column-context, + .column-date_modified { + width: 150px; + } +} + +table.email_templates { + margin-top: 1em; +} + +.edd-emails__tablenav--top { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + + p.search-box { + margin: 0; + } + + .edd-emails__actions { + margin-left: auto; + } +} + +.edd-list-table__name { + display: flex; + align-items: center; + gap: 6px; +} + +.edd-emails__wpsmtp { + display: flex; + align-items: center; + gap: 4px; + justify-content: flex-end; + + & a { + text-decoration: none; + color: $wp-text; + } + + img { + width: 20px; + height: auto; + } +} diff --git a/assets/css/admin/emails/style.scss b/assets/css/admin/emails/style.scss new file mode 100644 index 00000000000..ca6ed6f8151 --- /dev/null +++ b/assets/css/admin/emails/style.scss @@ -0,0 +1,7 @@ +@import "../../variables/variables"; +@import "../../components/toggle-button"; +@import "../../components/editor-header.scss"; +@import "promo"; +@import "table"; +@import "editor"; +@import "logs"; 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..7cb81eee6eb --- /dev/null +++ b/assets/css/admin/extensions/_buttons.scss @@ -0,0 +1,159 @@ +.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 { + .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; + } +} + +.edd-extension-manager__activate { + display: flex; + align-items: center; + gap: .5em; + min-height: 30px; + border: 1px solid $wp-border; + border-radius: 4px; + padding: 3px 10px; +} + +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..93a00dc5b14 --- /dev/null +++ b/assets/css/admin/extensions/_features.scss @@ -0,0 +1,12 @@ +.edd-extension-manager__features { + ul { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + text-align: left; + } + + .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..894c87de298 --- /dev/null +++ b/assets/css/admin/extensions/_group.scss @@ -0,0 +1,26 @@ +.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: 40px; + + p + & { + 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..a17ddf50046 --- /dev/null +++ b/assets/css/admin/extensions/_images.scss @@ -0,0 +1,22 @@ +.edd-extension-manager__icon { + background-color: $white; + 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/_overlay.scss b/assets/css/admin/extensions/_overlay.scss new file mode 100644 index 00000000000..2778137fb62 --- /dev/null +++ b/assets/css/admin/extensions/_overlay.scss @@ -0,0 +1,28 @@ +.edd-extension-manager__card--overlay { + text-align: left; + padding: 0; + + .edd-extension-manager__card { + padding: 0; + } + + .edd-extension-manager__title { + line-height: unset; + margin-bottom: .5em; + } + + .edd-extension-manager__actions { + background-color: $wp-alternate; + border-top: 1px solid $gray-200; + display: flex; + justify-content: flex-start; + align-items: center; + gap: .5em; + margin: 0 -2em -2em -2em; + padding: 16px 24px; + + .button { + margin: 0; + } + } +} diff --git a/assets/css/admin/extensions/_recommended.scss b/assets/css/admin/extensions/_recommended.scss new file mode 100644 index 00000000000..530f0322d1c --- /dev/null +++ b/assets/css/admin/extensions/_recommended.scss @@ -0,0 +1,21 @@ +.edd-extension-manager__card--installer { + &[data-filter*="recommended"] { + .edd-extension-manager__icon { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + } +} + +.edd-plugin__recommended { + font-size: 8px; + background: $wp-green-50; + color: $white; + margin-left: -1px; + margin-right: -1px; + padding: 6px 0; + border-radius: 0 0 4px 4px; + line-height: 1; + text-align: center; + width: 80px; +} 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..671d59e3e26 --- /dev/null +++ b/assets/css/admin/extensions/_variations.scss @@ -0,0 +1,70 @@ +/* Extension Manager Style Variations +--------------------------------------- */ +.edd-settings-wrap.has-product-education { + & .edd-settings-content { + width: 100%; + max-width: 100%; + } +} + +.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; + margin: 0 auto; + + & h3 { + font-size: 1.5rem; + margin-bottom: 10px; + text-align: center; + } + + & .edd-extension-manager__body { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 1em; + } +} + +.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..c9f5c1e3f91 --- /dev/null +++ b/assets/css/admin/extensions/style.scss @@ -0,0 +1,16 @@ +/* Extension Manager +------------------------------------------------------------- */ + +@import "bar"; +@import "body"; +@import "buttons"; +@import "card"; +@import "features"; +@import "group"; +@import "images"; +@import "installer"; +@import "overlay"; +@import "key"; +@import "steps"; +@import "variations"; +@import "recommended"; diff --git a/assets/css/admin/forms/_form-fields.scss b/assets/css/admin/forms/_form-fields.scss new file mode 100644 index 00000000000..898b71f0fce --- /dev/null +++ b/assets/css/admin/forms/_form-fields.scss @@ -0,0 +1,102 @@ +.edd-amount-type-wrapper { + display: inline-flex; + align-items: center; + background-color: $wp-alternate; + + input, + #edd-amount, + .edd-amount-input { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + margin-right: -2px !important; + width: unset; + + &.no-controls { + -moz-appearance:textfield; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + } + } + + select { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + width: auto !important; + + &.before { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; + } + } + + .edd__input { + width: unset; + + &--left { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; + margin-right: 0; + } + + &--right { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + margin-left: -2px; + } + } + + #edd-amount { + max-width: 125px; + } + + input:focus, + select:focus { + z-index: 2; + } + + .edd-input__symbol { + box-shadow: 0 0 0 transparent; + border-radius: 4px; + border: 1px solid #8c8f94; + background-color: #f5f5f5; + text-align: center; + padding: 0 8px; + align-self: stretch; + line-height: 2; + + @media screen and (max-width: $break-medium) { + padding: 3px 10px; + } + + &--prefix { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + margin-right: 0px; + } + + &--suffix { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + margin-left: -1px; + } + + + input { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + margin-right: inherit; + margin-left: -1px; + } + } +} diff --git a/assets/css/admin/forms/_form-group.scss b/assets/css/admin/forms/_form-group.scss new file mode 100644 index 00000000000..2ddc04e3c41 --- /dev/null +++ b/assets/css/admin/forms/_form-group.scss @@ -0,0 +1,131 @@ +/** + * Form Group + * + *
+ * + *
+ * + *
+ *

Help

+ *
+ * + * + *
+ * Label + * + *
+ * + * + *
+ * + *
+ * + * + *
+ *
+ * + */ +.edd-form-group { + margin-bottom: 16px; + + &:last-of-type { + margin-bottom: 0; + } + + legend { + margin-bottom: 8px; + } +} + +.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; + } + + &--row { + display: flex; + align-items: center; + gap: 8px; + } +} + +.edd-form-group__input { + max-width: 100%; + + &[type="checkbox"], + &[type="radio"] { + margin-top: 0; + + +label { + display: unset; + font-weight: 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; +} + +.edd-range { + display: flex; + align-items: center; + gap: 15px; + + .edd-range__slider { + min-width: 90px; + height: 2px; + border-radius: 10px; + border: none; + background: #cccccc; + + .ui-slider-range { + background: var(--wp-admin-theme-color); + } + + .ui-slider-handle { + height: 15px; + width: 15px; + top: -6.5px; + border-radius: 100%; + background: var(--wp-admin-theme-color); + border: none; + cursor: pointer; + display: inline-block; + position: relative; + } + } + + .edd-range__input { + max-width: 60px; + } +} 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/_general.scss b/assets/css/admin/forms/_general.scss new file mode 100644 index 00000000000..101d8f6480e --- /dev/null +++ b/assets/css/admin/forms/_general.scss @@ -0,0 +1,5 @@ +@media screen and (max-width: $break-mobile) { + .regular-text { + width: 100%; + } +} diff --git a/assets/css/admin/forms/style.scss b/assets/css/admin/forms/style.scss new file mode 100644 index 00000000000..f6ee239e4fb --- /dev/null +++ b/assets/css/admin/forms/style.scss @@ -0,0 +1,4 @@ +@import "form-group"; +@import "form-row"; +@import "general"; +@import "form-fields"; diff --git a/assets/css/admin/gateways/stripe.scss b/assets/css/admin/gateways/stripe.scss new file mode 100644 index 00000000000..74e2def7e13 --- /dev/null +++ b/assets/css/admin/gateways/stripe.scss @@ -0,0 +1,307 @@ +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/colors.native"; + +@import "../../variables/colors"; +@import "../../variables/animations"; + +/** + * 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-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, +); + +.edds-stripe-connect-acount-info { + & .display-name, + .info { + display: block; + } + + & .display-name { + font-weight: 700; + } + + &.loading { + & span { + display: block; + } + + & .account-name, + .info { + animation: skeleton-loading 1s linear infinite alternate; + width: 200px; + height: 14px; + margin: 0.25rem 0; + } + } +} + +#edds-stripe-disconnect-reconnect { + &.loading { + animation: skeleton-loading 1s linear infinite alternate; + width: 350px; + height: 1rem; + margin: 0.5rem 0; + } +} + +// Payment Gateways setting. +#edds-payment-gateways-stripe-unmet-requirements { + margin: -10px 0 0 -16px; + padding-top: 10px; + + input[type="checkbox"] { + margin: 0 6px 0 0; + } +} + +// Settings subtab. +.edds-requirements-not-met { + th { + display: none; + } + + td { + padding: 0; + } +} + +/* + * Stripe Connect + */ +.edd-stripe-connect { + display: inline-block; + margin-bottom: 1px; + + background-image: -webkit-linear-gradient(#28a0e5, #015e94); + background-image: -moz-linear-gradient(#28a0e5, #015e94); + background-image: -ms-linear-gradient(#28a0e5, #015e94); + background-image: linear-gradient(#28a0e5, #015e94); + + -webkit-font-smoothing: antialiased; + border: 0; + padding: 1px; + height: 30px; + text-decoration: none; + + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + + -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); + + cursor: pointer; + + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.edd-stripe-connect span { + display: block; + position: relative; + padding: 0 12px 0 44px; + height: 30px; + + background: #1275ff; + background-image: -webkit-linear-gradient(#7dc5ee, #008cdd 85%, #30a2e4); + background-image: -moz-linear-gradient(#7dc5ee, #008cdd 85%, #30a2e4); + background-image: -ms-linear-gradient(#7dc5ee, #008cdd 85%, #30a2e4); + background-image: linear-gradient(#7dc5ee, #008cdd 85%, #30a2e4); + + font-size: 14px; + line-height: 30px; + color: white; + font-weight: bold; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); + + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); + + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +.edd-stripe-connect span:before { + content: ""; + display: block; + position: absolute; + left: 11px; + top: 50%; + width: 23px; + height: 24px; + margin-top: -12px; + background-repeat: no-repeat; + background-size: 23px 24px; +} + +.edd-stripe-connect:active { + background: #005d93; +} + +.edd-stripe-connect:active span { + color: #eee; + + background: #008cdd; + background-image: -webkit-linear-gradient(#008cdd, #008cdd 85%, #239adf); + background-image: -moz-linear-gradient(#008cdd, #008cdd 85%, #239adf); + background-image: -ms-linear-gradient(#008cdd, #008cdd 85%, #239adf); + background-image: linear-gradient(#008cdd, #008cdd 85%, #239adf); + + -moz-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1); + -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1); +} + +.edd-stripe-connect:active span:before { +} + +.edd-stripe-connect span:before, +.edd-stripe-connect.blue span:before { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAYCAYAAAARfGZ1AAAKRGlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUFNcXx9/MbC+0XZYiZem9twWkLr1IlSYKy+4CS1nWZRewN0QFIoqICFYkKGLAaCgSK6JYCAgW7AEJIkoMRhEVlczGHPX3Oyf5/U7eH3c+8333nnfn3vvOGQAoASECYQ6sAEC2UCKO9PdmxsUnMPG9AAZEgAM2AHC4uaLQKL9ogK5AXzYzF3WS8V8LAuD1LYBaAK5bBIQzmX/p/+9DkSsSSwCAwtEAOx4/l4tyIcpZ+RKRTJ9EmZ6SKWMYI2MxmiDKqjJO+8Tmf/p8Yk8Z87KFPNRHlrOIl82TcRfKG/OkfJSREJSL8gT8fJRvoKyfJc0WoPwGZXo2n5MLAIYi0yV8bjrK1ihTxNGRbJTnAkCgpH3FKV+xhF+A5gkAO0e0RCxIS5cwjbkmTBtnZxYzgJ+fxZdILMI53EyOmMdk52SLOMIlAHz6ZlkUUJLVlokW2dHG2dHRwtYSLf/n9Y+bn73+GWS9/eTxMuLPnkGMni/al9gvWk4tAKwptDZbvmgpOwFoWw+A6t0vmv4+AOQLAWjt++p7GLJ5SZdIRC5WVvn5+ZYCPtdSVtDP6386fPb8e/jqPEvZeZ9rx/Thp3KkWRKmrKjcnKwcqZiZK+Jw+UyL/x7ifx34VVpf5WEeyU/li/lC9KgYdMoEwjS03UKeQCLIETIFwr/r8L8M+yoHGX6aaxRodR8BPckSKPTRAfJrD8DQyABJ3IPuQJ/7FkKMAbKbF6s99mnuUUb3/7T/YeAy9BXOFaQxZTI7MprJlYrzZIzeCZnBAhKQB3SgBrSAHjAGFsAWOAFX4Al8QRAIA9EgHiwCXJAOsoEY5IPlYA0oAiVgC9gOqsFeUAcaQBM4BtrASXAOXARXwTVwE9wDQ2AUPAOT4DWYgSAID1EhGqQGaUMGkBlkC7Egd8gXCoEioXgoGUqDhJAUWg6tg0qgcqga2g81QN9DJ6Bz0GWoH7oDDUPj0O/QOxiBKTAd1oQNYSuYBXvBwXA0vBBOgxfDS+FCeDNcBdfCR+BW+Bx8Fb4JD8HP4CkEIGSEgeggFggLYSNhSAKSioiRlUgxUonUIk1IB9KNXEeGkAnkLQaHoWGYGAuMKyYAMx/DxSzGrMSUYqoxhzCtmC7MdcwwZhLzEUvFamDNsC7YQGwcNg2bjy3CVmLrsS3YC9ib2FHsaxwOx8AZ4ZxwAbh4XAZuGa4UtxvXjDuL68eN4KbweLwa3gzvhg/Dc/ASfBF+J/4I/gx+AD+Kf0MgE7QJtgQ/QgJBSFhLqCQcJpwmDBDGCDNEBaIB0YUYRuQRlxDLiHXEDmIfcZQ4Q1IkGZHcSNGkDNIaUhWpiXSBdJ/0kkwm65KdyRFkAXk1uYp8lHyJPEx+S1GimFLYlESKlLKZcpBylnKH8pJKpRpSPakJVAl1M7WBep76kPpGjiZnKRcox5NbJVcj1yo3IPdcnihvIO8lv0h+qXyl/HH5PvkJBaKCoQJbgaOwUqFG4YTCoMKUIk3RRjFMMVuxVPGw4mXFJ0p4JUMlXyWeUqHSAaXzSiM0hKZHY9O4tHW0OtoF2igdRzeiB9Iz6CX07+i99EllJWV75RjlAuUa5VPKQwyEYcgIZGQxyhjHGLcY71Q0VbxU+CqbVJpUBlSmVeeoeqryVYtVm1Vvqr5TY6r5qmWqbVVrU3ugjlE3VY9Qz1ffo35BfWIOfY7rHO6c4jnH5tzVgDVMNSI1lmkc0OjRmNLU0vTXFGnu1DyvOaHF0PLUytCq0DqtNa5N03bXFmhXaJ/RfspUZnoxs5hVzC7mpI6GToCOVGe/Tq/OjK6R7nzdtbrNug/0SHosvVS9Cr1OvUl9bf1Q/eX6jfp3DYgGLIN0gx0G3QbThkaGsYYbDNsMnxipGgUaLTVqNLpvTDX2MF5sXGt8wwRnwjLJNNltcs0UNnUwTTetMe0zg80czQRmu836zbHmzuZC81rzQQuKhZdFnkWjxbAlwzLEcq1lm+VzK32rBKutVt1WH60drLOs66zv2SjZBNmstemw+d3W1JZrW2N7w45q52e3yq7d7oW9mT3ffo/9bQeaQ6jDBodOhw+OTo5ixybHcSd9p2SnXU6DLDornFXKuuSMdfZ2XuV80vmti6OLxOWYy2+uFq6Zroddn8w1msufWzd3xE3XjeO2323Ineme7L7PfchDx4PjUevxyFPPk+dZ7znmZeKV4XXE67m3tbfYu8V7mu3CXsE+64P4+PsU+/T6KvnO9632fein65fm1+g36e/gv8z/bAA2IDhga8BgoGYgN7AhcDLIKWhFUFcwJTgquDr4UYhpiDikIxQODQrdFnp/nsE84by2MBAWGLYt7EG4Ufji8B8jcBHhETURjyNtIpdHdkfRopKiDke9jvaOLou+N994vnR+Z4x8TGJMQ8x0rE9seexQnFXcirir8erxgvj2BHxCTEJ9wtQC3wXbF4wmOiQWJd5aaLSwYOHlReqLshadSpJP4iQdT8YmxyYfTn7PCePUcqZSAlN2pUxy2dwd3Gc8T14Fb5zvxi/nj6W6pZanPklzS9uWNp7ukV6ZPiFgC6oFLzICMvZmTGeGZR7MnM2KzWrOJmQnZ58QKgkzhV05WjkFOf0iM1GRaGixy+LtiyfFweL6XCh3YW67hI7+TPVIjaXrpcN57nk1eW/yY/KPFygWCAt6lpgu2bRkbKnf0m+XYZZxl3Uu11m+ZvnwCq8V+1dCK1NWdq7SW1W4anS1/+pDa0hrMtf8tNZ6bfnaV+ti13UUahauLhxZ77++sUiuSFw0uMF1w96NmI2Cjb2b7Dbt3PSxmFd8pcS6pLLkfSm39Mo3Nt9UfTO7OXVzb5lj2Z4tuC3CLbe2emw9VK5YvrR8ZFvottYKZkVxxavtSdsvV9pX7t1B2iHdMVQVUtW+U3/nlp3vq9Orb9Z41zTv0ti1adf0bt7ugT2ee5r2au4t2ftun2Df7f3++1trDWsrD+AO5B14XBdT1/0t69uGevX6kvoPB4UHhw5FHupqcGpoOKxxuKwRbpQ2jh9JPHLtO5/v2pssmvY3M5pLjoKj0qNPv0/+/tax4GOdx1nHm34w+GFXC62luBVqXdI62ZbeNtQe395/IuhEZ4drR8uPlj8ePKlzsuaU8qmy06TThadnzyw9M3VWdHbiXNq5kc6kznvn487f6Iro6r0QfOHSRb+L57u9us9ccrt08rLL5RNXWFfarjpebe1x6Gn5yeGnll7H3tY+p772a87XOvrn9p8e8Bg4d93n+sUbgTeu3px3s//W/Fu3BxMHh27zbj+5k3Xnxd28uzP3Vt/H3i9+oPCg8qHGw9qfTX5uHnIcOjXsM9zzKOrRvRHuyLNfcn95P1r4mPq4ckx7rOGJ7ZOT437j154ueDr6TPRsZqLoV8Vfdz03fv7Db56/9UzGTY6+EL+Y/b30pdrLg6/sX3VOhU89fJ39ema6+I3am0NvWW+738W+G5vJf49/X/XB5EPHx+CP92ezZ2f/AAOY8/wRDtFgAAADQklEQVRIDbWVaUiUQRjHZ96dXY/d1fYQj1U03dJSw9YkFgy6DIkILRArQSSC7PjQjQQqVH7oQ0GHQUWgpQhKHzoNSqiUwpXcsrwIjzVtPVrzbPV9Z6bZhYV3N3WXYAeGmWeeZ37z8J95GEgpBf5oeXn1Es4fYAdzPDlM6je4RBYhR+LMU89UxiCBGiCgkUwsBYSA+SlPKLQBQAYEAZm+3j42K96z3NyOF7VOeMrp62opRcacjPW5+43rDTpNSKQ8QKZAEg7xmPCTs/O27uGJgXuNbW0pxyvLfTmAEBzthEsFZLxRvPdi5rpYo2cmUiQJDA4IVeo0obGdlvGfXUPj0Sym2zPuHxvzcWjDyVupJ/YYizKTGNjLw/HiduNTAqIRIUJ6Vpp+ky8bCSFgwQ2xgkGxFi1ioNWEBGuJB31gbLIv/2pd7SpFoGxtpCYkLSEq4ptlzIYFO7tc7w0TKkeEYg5ADnrWkkYhD8s26GPq3nW0WKxTptftPYBI4Mj3O2fHvKNZBMVSDmMwarXNjDkSF3d5kExZeiCr8M2VI+VFu9IvsPcYtzAvkfoEZkEEE45jMppq3ppbCNPFIY1nD1cpo07lbMmvOXeoDCF8BLKy9uUAAjDkBh+c6bz78mNtVVP7MwET7JBnqb4xXpdWVpC1OVzWn+ELHLCsneX/s7rkRWl1463cy1U3WroG21jhCGKJXPOtKQnpAuENvsAppgDB3TcDVIrpDHbK5Kd+y7W8iodNybHh22rOHyxUK+UaMYjZaoyp25rYL54TSihSKmwZ14v3lc3ZFxdbeywjn/tGJnkmzrydX1ApxOEACKymmXLYfXVpi1JMEOGxPi1ep18doY4r2J7uFumQQ9yGf01bMcZW8dpyc0oIjxxpuC5wuUDX+ovWrnYeg3aXvdLIqnmOvXPsfH6uA5YbTb1DX8ofvTLzTy6ZV4K6fAw+gXiATfdffmjeaUgc1UdpdWplsCooQBrEnqUw82dhdnjit/Vxc4f59tP3DRjzJvYteqrl4rmNlJIfrOwpgNklesDRNQBCHYtQAQqD2CgACNjHAJnG1EyfV/S67fZiJB5t2OGEe4n7L3fS4fpEv/2hUEATfoPbuam5v8N7nps70YTbAAAAAElFTkSuQmCC"); +} + +.edds-stripe-connect-acount-info span.edd-pro-upgrade a { + color: $edd-pro-upgrade; + font-weight: 600; + text-decoration: none; +} + +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), + only screen and (min--moz-device-pixel-ratio: 1.5), + only screen and (min-device-pixel-ratio: 1.5) { + .edd-stripe-connect span:before, + .edd-stripe-connect.blue span:before { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAwCAYAAABuZUjcAAAKRGlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUFNcXx9/MbC+0XZYiZem9twWkLr1IlSYKy+4CS1nWZRewN0QFIoqICFYkKGLAaCgSK6JYCAgW7AEJIkoMRhEVlczGHPX3Oyf5/U7eH3c+8333nnfn3vvOGQAoASECYQ6sAEC2UCKO9PdmxsUnMPG9AAZEgAM2AHC4uaLQKL9ogK5AXzYzF3WS8V8LAuD1LYBaAK5bBIQzmX/p/+9DkSsSSwCAwtEAOx4/l4tyIcpZ+RKRTJ9EmZ6SKWMYI2MxmiDKqjJO+8Tmf/p8Yk8Z87KFPNRHlrOIl82TcRfKG/OkfJSREJSL8gT8fJRvoKyfJc0WoPwGZXo2n5MLAIYi0yV8bjrK1ihTxNGRbJTnAkCgpH3FKV+xhF+A5gkAO0e0RCxIS5cwjbkmTBtnZxYzgJ+fxZdILMI53EyOmMdk52SLOMIlAHz6ZlkUUJLVlokW2dHG2dHRwtYSLf/n9Y+bn73+GWS9/eTxMuLPnkGMni/al9gvWk4tAKwptDZbvmgpOwFoWw+A6t0vmv4+AOQLAWjt++p7GLJ5SZdIRC5WVvn5+ZYCPtdSVtDP6386fPb8e/jqPEvZeZ9rx/Thp3KkWRKmrKjcnKwcqZiZK+Jw+UyL/x7ifx34VVpf5WEeyU/li/lC9KgYdMoEwjS03UKeQCLIETIFwr/r8L8M+yoHGX6aaxRodR8BPckSKPTRAfJrD8DQyABJ3IPuQJ/7FkKMAbKbF6s99mnuUUb3/7T/YeAy9BXOFaQxZTI7MprJlYrzZIzeCZnBAhKQB3SgBrSAHjAGFsAWOAFX4Al8QRAIA9EgHiwCXJAOsoEY5IPlYA0oAiVgC9gOqsFeUAcaQBM4BtrASXAOXARXwTVwE9wDQ2AUPAOT4DWYgSAID1EhGqQGaUMGkBlkC7Egd8gXCoEioXgoGUqDhJAUWg6tg0qgcqga2g81QN9DJ6Bz0GWoH7oDDUPj0O/QOxiBKTAd1oQNYSuYBXvBwXA0vBBOgxfDS+FCeDNcBdfCR+BW+Bx8Fb4JD8HP4CkEIGSEgeggFggLYSNhSAKSioiRlUgxUonUIk1IB9KNXEeGkAnkLQaHoWGYGAuMKyYAMx/DxSzGrMSUYqoxhzCtmC7MdcwwZhLzEUvFamDNsC7YQGwcNg2bjy3CVmLrsS3YC9ib2FHsaxwOx8AZ4ZxwAbh4XAZuGa4UtxvXjDuL68eN4KbweLwa3gzvhg/Dc/ASfBF+J/4I/gx+AD+Kf0MgE7QJtgQ/QgJBSFhLqCQcJpwmDBDGCDNEBaIB0YUYRuQRlxDLiHXEDmIfcZQ4Q1IkGZHcSNGkDNIaUhWpiXSBdJ/0kkwm65KdyRFkAXk1uYp8lHyJPEx+S1GimFLYlESKlLKZcpBylnKH8pJKpRpSPakJVAl1M7WBep76kPpGjiZnKRcox5NbJVcj1yo3IPdcnihvIO8lv0h+qXyl/HH5PvkJBaKCoQJbgaOwUqFG4YTCoMKUIk3RRjFMMVuxVPGw4mXFJ0p4JUMlXyWeUqHSAaXzSiM0hKZHY9O4tHW0OtoF2igdRzeiB9Iz6CX07+i99EllJWV75RjlAuUa5VPKQwyEYcgIZGQxyhjHGLcY71Q0VbxU+CqbVJpUBlSmVeeoeqryVYtVm1Vvqr5TY6r5qmWqbVVrU3ugjlE3VY9Qz1ffo35BfWIOfY7rHO6c4jnH5tzVgDVMNSI1lmkc0OjRmNLU0vTXFGnu1DyvOaHF0PLUytCq0DqtNa5N03bXFmhXaJ/RfspUZnoxs5hVzC7mpI6GToCOVGe/Tq/OjK6R7nzdtbrNug/0SHosvVS9Cr1OvUl9bf1Q/eX6jfp3DYgGLIN0gx0G3QbThkaGsYYbDNsMnxipGgUaLTVqNLpvTDX2MF5sXGt8wwRnwjLJNNltcs0UNnUwTTetMe0zg80czQRmu836zbHmzuZC81rzQQuKhZdFnkWjxbAlwzLEcq1lm+VzK32rBKutVt1WH60drLOs66zv2SjZBNmstemw+d3W1JZrW2N7w45q52e3yq7d7oW9mT3ffo/9bQeaQ6jDBodOhw+OTo5ixybHcSd9p2SnXU6DLDornFXKuuSMdfZ2XuV80vmti6OLxOWYy2+uFq6Zroddn8w1msufWzd3xE3XjeO2323Ineme7L7PfchDx4PjUevxyFPPk+dZ7znmZeKV4XXE67m3tbfYu8V7mu3CXsE+64P4+PsU+/T6KvnO9632fein65fm1+g36e/gv8z/bAA2IDhga8BgoGYgN7AhcDLIKWhFUFcwJTgquDr4UYhpiDikIxQODQrdFnp/nsE84by2MBAWGLYt7EG4Ufji8B8jcBHhETURjyNtIpdHdkfRopKiDke9jvaOLou+N994vnR+Z4x8TGJMQ8x0rE9seexQnFXcirir8erxgvj2BHxCTEJ9wtQC3wXbF4wmOiQWJd5aaLSwYOHlReqLshadSpJP4iQdT8YmxyYfTn7PCePUcqZSAlN2pUxy2dwd3Gc8T14Fb5zvxi/nj6W6pZanPklzS9uWNp7ukV6ZPiFgC6oFLzICMvZmTGeGZR7MnM2KzWrOJmQnZ58QKgkzhV05WjkFOf0iM1GRaGixy+LtiyfFweL6XCh3YW67hI7+TPVIjaXrpcN57nk1eW/yY/KPFygWCAt6lpgu2bRkbKnf0m+XYZZxl3Uu11m+ZvnwCq8V+1dCK1NWdq7SW1W4anS1/+pDa0hrMtf8tNZ6bfnaV+ti13UUahauLhxZ77++sUiuSFw0uMF1w96NmI2Cjb2b7Dbt3PSxmFd8pcS6pLLkfSm39Mo3Nt9UfTO7OXVzb5lj2Z4tuC3CLbe2emw9VK5YvrR8ZFvottYKZkVxxavtSdsvV9pX7t1B2iHdMVQVUtW+U3/nlp3vq9Orb9Z41zTv0ti1adf0bt7ugT2ee5r2au4t2ftun2Df7f3++1trDWsrD+AO5B14XBdT1/0t69uGevX6kvoPB4UHhw5FHupqcGpoOKxxuKwRbpQ2jh9JPHLtO5/v2pssmvY3M5pLjoKj0qNPv0/+/tax4GOdx1nHm34w+GFXC62luBVqXdI62ZbeNtQe395/IuhEZ4drR8uPlj8ePKlzsuaU8qmy06TThadnzyw9M3VWdHbiXNq5kc6kznvn487f6Iro6r0QfOHSRb+L57u9us9ccrt08rLL5RNXWFfarjpebe1x6Gn5yeGnll7H3tY+p772a87XOvrn9p8e8Bg4d93n+sUbgTeu3px3s//W/Fu3BxMHh27zbj+5k3Xnxd28uzP3Vt/H3i9+oPCg8qHGw9qfTX5uHnIcOjXsM9zzKOrRvRHuyLNfcn95P1r4mPq4ckx7rOGJ7ZOT437j154ueDr6TPRsZqLoV8Vfdz03fv7Db56/9UzGTY6+EL+Y/b30pdrLg6/sX3VOhU89fJ39ema6+I3am0NvWW+738W+G5vJf49/X/XB5EPHx+CP92ezZ2f/AAOY8/wRDtFgAAAIbklEQVRoBdVZa5BURxU+fZ9z57mzs7PvF4i7srAQSCifMVDERC0jYlzUlJalKeGPlCnL/NEfywpWacoiVZRVJIYfGjGUu5bxj5qHFSAYyQOBEsJzYSHDvnd2dp535j66PX1vNgsULDPs1cr2Vs+9e7v79NfnnnP663MJYwwWYxEWI2iOedEClxabxgkBwjEvOuA9PQOOlSw64JMr4vK8GidYYMcOES4tVSEAAZ8FAUqon1GiAJEEEG0CjFB8cTaxZUMAo1gEqQA0UABprAjPbrUwXnkesgqKP8CBk5vDIenrE+BKmwI+MawA1MbCkdV10cBDflXuVmSxQRbFkCAQZ9U2ZTaONyxKcyXDHjMs83ImV3rz6njmDRPMUZB80zAJOuvvsflkXpTP7DrWyeXcYCqk75AEieawrEoty1vrvlcV0ja3VQdb1rVUQVd9EFqqNIj5ZfDJooPBsCnohq2ldDMynC42XZnW7z09lu25lMxDMl34y0gyvTsBwyewc84Z4MEPpWIzF/MBcLLtNzJISmxZU+PmWETbtqGzfvVja5uguyF02+kCIEJUk6Ex4oMV9XP9ZnQT/nZ24it7XrtoJ5LZ7SjAM+Bg2+0ckAOcbBkQIaZFVzY1bGurjezYfn87PNQZ5+13ZaQRXMzH26Lg8ymfUokQdAR59INOc53GQ6q/Jiiua6oJ7+h9uAPua47cHeLrwHEmQRmTGLHV6x4v+JYwWsOFCGRDn6RKem1rPPrkN9Y0uqAXLN4VwCgjYGEE8rBgMAjwKsF9S9WgLa9qjYcf+Po9jXdlGrfC5Wj8Vg0Lf+ZENAFmpGB9TWTLhmUxUD1UDg/gtudRnK+a4RtkgqQyO+RT5LVrmiLgJcN19gcGNojUWriS5yRQm7pcBTc/vyCKdW1RrWwzOTiYhGf+dRUmcgZosgDVfgWaMCS2V2tO+OzG0MiVjdUwiFiYm9a7O4kJAoZEooV9H4T0O0ofODkKr5+6+nY6V3heVZQpv6ZWaz55qSJJnXjtUBW5pT7k8xeK5u+B0PQdBVbQgTLq9HbQYthyNVSmTT6A/nB0aGpF0K99+trY1F7TNI9PZGXkKUVRtYjGZCIOV1dHR4Ynz8FSLV8BrjK6uiAlpLcmco1ipmgpAaU8rfesboCuumBg31uJbx6+qH0uX9D/em0i85xFhaslKZKA8/82RtYDhd/1MkCuBnjxrLgKB0EQSb5oWO+9O1bZrsy3+Kc3dcH+b99b07NuyXe6P9r8z/am+C9lkuqCjo4qGGkQES76qJcuz/2GOlUoFuVsQS+98frlaSeq8Gkqqctrg7Dz853wwrfugUfXtj3W3tJ8oCletRUEXy1SCSSYHhdu41gFqILcZCrzwkvnJmE0U3JtHefiL7eS2l7th11f7IQ9j65aVh+r+nlzbd2TELJrHPLmIXZX3wyBX8MTQMm8PJ0u9Pe9chGQYy9omvXouHu/thJqI+Ef1sZDm0AMBmfPiQsSPDuY2zhWwSH5ISU5Pjm98x9nRo7+7JVBB3wl5nJz35Vo/z/esBQUVf2+QlkD9Aw42/Ts3Au7ushdAhQ5UzJoOjE+OrV9/1tDR7cNnIax7N2bDX9nm1bUQXdz9Rp/MLwRoqAtDOzcaO7rvDrAWW8vhcatWVNjF6cmJre9embkz1947h3YfXgIUgVzblQldxgFH0ZOr/qULwM15k4Zlci4Vd9ZU5ltY71oObHBnBFQBidmUk8kEsOP7Hntwqsb974NfS8PAh7LKoo23Hw+2R4FQcSzKlDPgFOEyf8kx3HW94kQ7xJgRRdAJG7CyIWxgiXNUN0+k5nJLN83k3n8D8eHN3+1ux5+8uBHIKiWt1G1Rn3IJkiUCcQzU3G0h9qWHMeJdoSrwtr9dl6I6DNjFwRRyxiKnStSqkPJPsGSmZ+mp1P9z2dzOy3Klj31yMdmX9S8V75APEsomMZwT9fz9i6vkW9AvEgQyqrBQM2Dq9rrD0gCgXfHA0jpjIRm2Zcw+3CR2tZl27SnMZFSZ1lWcRwZITeDckresAEXaoKwwBh7/WQubgTOQj5BVjdv7KiBJz7bztMNcHIk03JiONNyfiK/ntv2VMHAMx6BjpoA/Gj9Emdjul7W7e6TeQNDK9WJLRm361P5c1drEmAaymaYoXpfjZoiOk7FHWuh5dxEHmzLHiXM9oyTz9FawRZw65f5yyzXBMpd0JGhFKB5nSwRMVvumDv2cxm4m1f5X4AuWhRePDUOtqEPQJVVGfWcBz1ahmPlTlxzqaJLquYZU1HTvjcTMD6dOULM0n+g5nKposHzdWbo7FgEkDBviWlYx++53XtQ33kvDU8dHAJm6L8usdwEZn09S3qiPed5lcCSLUpI0eEA8620zLbDl6bh8T+egkI+/7Rl6kegcTSPst1QUKaM+brhrjnF2yUQJNxnrGMnR7KbTw5nYFVjyAl98w2+VdvVlA67Dw3BgROjAKa+yyrpz0BKTbJnez1NT6AKrrnA1bEi1av2v3xaiL90dnxL2Kc0rsXc4WpcQEc8AEtiGrRiejmK6WWeMDIxtVwwKExijB5KFuBYIg1cy8dx0dTQ/yQVc78yBXMIqJ5i/VvvkqHdSjXuM/THKy7w2LQJ6fpJms38QiHGvlzBt+RwJv2JQ2elbjyRtjIi1AIRMAsKPuQduHVzr2YW+kIBE5BTwOzzxLKOiMX8QVuWh00IpqD+S0WHtLlzefpLBOZo/IYvEqQPnTX5dxmy4xookqaCjRuT4mMi8g3bxs2KCkj3GFj4+QSzA0RkeskU8iCJeUiBDv09Jt8OPEV6k7DlP3gxxh/dAPymPh/Kf5d897dIOd9P7H8oEd4G1JV8wPGbRadx52sgLmrRAZ99EZ5+LZgV+v+4Llrg/wX6HRCxgvzAAwAAAABJRU5ErkJggg=="); + } +} +/* End of Stripe Connect */ + +.edd-text-loading { + animation: skeleton-loading 1s infinite alternate; +} + +.edd-button__toggle { + position: relative; + margin: 0; + padding: 0; + width: 36px; + height: 20px; + min-height: unset; + transition: background 0.2s ease; + border-radius: 30px; + box-shadow: none; + border: none; + display: flex; + justify-content: center; + align-items: center; + background: $wp-gray-10; + margin-right: 12px; + + &--enabled { + background: var(--wp-admin-theme-color); + + &::after { + transform: translateX(16px); + } + } +} + +.edd-button__toggle:after { + position: absolute; + content: ""; + height: 14px; + width: 14px; + left: 3px; + bottom: 3px; + background-color: $white; + transition: 0.1s transform ease; + border-radius: 50%; + background: white; +} + +.edd-stripe-payment-methods__description { + .button-group { + display: block !important; + margin: 20px auto; + text-align: center; + } +} + +.edd_settingsstripe_payment_methods { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1em; + + .edd-form-group__control--wrap { + background-color: $white; + border: 1px solid $wp-gray-10; + border-radius: 4px; + padding: 20px; + margin: 0; + justify-content: space-between; + + .edd-toggle { + gap: 8px; + margin-bottom: 0; + } + + .edd-form-group__control__label { + flex-grow: 1; + display: flex; + align-items: center; + gap: 8px; + justify-content: space-between; + + label { + flex-grow: 1; + } + } + + .description { + flex-basis: 100%; + } + + .edd-icon__placeholder, + > svg { + width: 32px; + height: 32px; + } + + input[type="checkbox"] { + order: 100; + } + + input[type="checkbox"]:disabled:not(:checked) { + background-color: rgba($wp-red-50, 0.5); + } + } +} diff --git a/assets/css/admin/gateways/style.scss b/assets/css/admin/gateways/style.scss new file mode 100644 index 00000000000..364609bf984 --- /dev/null +++ b/assets/css/admin/gateways/style.scss @@ -0,0 +1,40 @@ +/* PayPal Connect +-------------------------------------------------------------- */ +#edd-paypal-commerce-connect-wrap.loading { + & ul.edd-paypal-account-status, ul.edd-paypal-webhook-events { + & li { + & span { + animation: skeleton-loading 1s infinite alternate; + width: 250px; + height: 18px; + display: inline-block; + } + } + } + + & .edd-paypal-connect-actions { + & span { + animation: skeleton-loading 1s infinite alternate; + width: 150px; + height: 32px; + display: inline-block; + } + } +} + +.edd-paypal-account-status ul { + margin-left: 25px; + list-style-type: none; +} +.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/_admin.scss b/assets/css/admin/general/_admin.scss new file mode 100644 index 00000000000..c889ca16802 --- /dev/null +++ b/assets/css/admin/general/_admin.scss @@ -0,0 +1,62 @@ +body.edd-admin-page { + + @media (min-width: $break-medium) { + & #wpbody-content { + padding-bottom: 200px; + } + } + + & #wpfooter { + .edd-footer-promotion { + text-align: center; + font-weight: 400; + font-size: 13px; + line-height: 16px; + color: $wp-gray-40; + padding: 20px 0 30px 0; + margin-bottom: 20px; + margin-top: 20px; + + & p { + font-weight: 600; + } + + & .edd-footer-promotion-links, .edd-footer-promotion-social { + display: flex; + justify-content: center; + align-items: center; + } + + & .edd-footer-promotion-links { + margin: 9px 0 0; + + & span { + color: $wp-gray-10; + padding: 0 7px; + } + } + + & .edd-footer-promotion-social { + margin: 10px 0 0 0; + gap: 10px; + + & li { + margin-bottom: 0; + + & path { + fill: $edd-footer-social-link; + } + + &:hover path { + fill: $edd-footer-social-link-hover; + } + } + + & a { + display: block; + height: 16px; + } + } + } + } +} 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/_elements.scss b/assets/css/admin/general/_elements.scss new file mode 100644 index 00000000000..406f3b7277f --- /dev/null +++ b/assets/css/admin/general/_elements.scss @@ -0,0 +1,25 @@ +.edd-hidden { + display: none; + + &--required { + display: none !important; + } +} + +.edd-clearfix:after { + content: ""; + display: table; + clear: both; +} + +.edd-fadein { + visibility: visible; + opacity: 1; + transition: opacity 1s linear; +} + +.edd-fadeout { + visibility: hidden; + opacity: 0; + transition: visibility 0s 1s, opacity 1s linear; +} diff --git a/assets/css/admin/general/_grid.scss b/assets/css/admin/general/_grid.scss new file mode 100644 index 00000000000..58a2f2cc773 --- /dev/null +++ b/assets/css/admin/general/_grid.scss @@ -0,0 +1,46 @@ +.edd-admin--has-grid { + display: grid; + display: -ms-grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + grid-gap: 20px; + + .postbox { + margin-bottom: 0; + } + + .edd-from-to-wrapper { + display: flex; + margin-bottom: 16px; + width: 100%; + + input { + width: 100%; + } + + span { + flex-grow: 1; + } + } + + form { + display: flex; + flex-direction: column; + flex-wrap: wrap; + position: relative; + } + + .postbox .edd-select { + max-width: 100%; + margin-right: 0; + } + + .button.updating-message:before, + .button.updated-message:before { + vertical-align: text-bottom; + margin: 0 5px 0 0; + } + + @media screen and (max-width: $break-mobile) { + grid-template-columns: 1fr; + } +} diff --git a/assets/css/admin/general/_help-tip.scss b/assets/css/admin/general/_help-tip.scss new file mode 100644 index 00000000000..80674e80140 --- /dev/null +++ b/assets/css/admin/general/_help-tip.scss @@ -0,0 +1,65 @@ +.edd-help-tip { + cursor: help; + margin-top: -2px; + font-size: 24px; + color: $edd-tool-tip-icon-color; +} + +.edd-ui-tooltip { + position: absolute; + background: $white !important; + border-width: 0px; + border-radius: 12px !important; + box-shadow: $edd-tool-tip-shadow !important; + color: $edd-tooltip-text !important; + max-width: 300px !important; + padding: 16px !important; + text-rendering: optimizeLegibility; + text-shadow: none !important; + font-size: 13px !important; + z-index: 9999 !important; + + .title { + font-weight: bold; + } + + .timeline { + position: relative; + margin: 6px 0 0; + padding-left: 15px; + + li { + position: relative; + margin: 0 0 3px 0; + + &::before { + content: ''; + position: absolute; + width: 4px; + height: 4px; + left: -16px; + background: rgb(0 0 0 / 0%); + border: 2px solid $edd-tooltip-text; + top: 0; + bottom: 0; + margin: auto; + border-radius: 100%; + z-index: 1; + } + + &::after { + content: ''; + width: 2px; + height: calc(100% - 4px); + background: $edd-tooltip-text; + position: absolute; + left: -13px; + top: calc(50% + 3px); + } + + &:last-child::after { + 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/_item-tab-wrapper.scss b/assets/css/admin/general/_item-tab-wrapper.scss new file mode 100644 index 00000000000..7cab1a387a8 --- /dev/null +++ b/assets/css/admin/general/_item-tab-wrapper.scss @@ -0,0 +1,63 @@ +#edd-item-tab-wrapper { + line-height: 1em; + margin: 0 -1px 0 0; + padding: 0; + background-color: #f5f5f5; + box-sizing: border-box; + + li { + display: block; + margin: 0; + padding: 0; + background-color: #fcfcfc; + + &.edd-hidden { + display: none; + } + + a, + > .edd-item-tab-label-wrap { + display: flex; + margin: 0; + padding: 9px; + text-decoration: none; + border-bottom: 1px solid #e5e5e5; + box-shadow: none; + position: relative; + align-items: center; + gap: 3px; + color: $gray-60; + } + + >.edd-item-tab-label-wrap { + background-color: $white; + } + + a:focus, + a:hover { + box-shadow: inset 5px 0; + outline: 0; + transition: all .25s; + } + } +} + +#edd-item-tab-wrapper-list { + margin: 0; +} + +@media only screen and (max-width: $break-medium) { + + #edd-item-tab-wrapper { + width: 48px; + + .edd-item-tab-label { + overflow: hidden; + position: absolute; + top: -1000em; + left: -1000em; + width: 1px; + height: 1px; + } + } +} diff --git a/assets/css/admin/general/_progress-bar.scss b/assets/css/admin/general/_progress-bar.scss new file mode 100644 index 00000000000..a37d7c4d71b --- /dev/null +++ b/assets/css/admin/general/_progress-bar.scss @@ -0,0 +1,45 @@ +@import "../../variables/colors"; +.edd-progress-bar { + display: grid; + background: $wp-gray-5; + border-radius: 99999px; + padding: 2px; + box-shadow: inset 0 0 1px 1px $wp-input-border; + align-items: center; + + &.small { + height: 14px; + } + + &.medium { + height: 16px; + } + + &.large { + height: 20px; + padding: 4px; + } + + > .progress { + height: 100%; + border-top-right-radius: 99999px; + border-bottom-right-radius: 99999px; + border-top-left-radius: 99999px; + border-bottom-left-radius: 99999px; + background-color: rgba($wp-green-30, 0.3); + overflow: hidden; + min-width: 10%; + width: var(--progress-width, 0%); + grid-area: 1/-1; + } + + > .label { + color: $wp-input-text; + font-weight: 400; + font-size: .75rem; + text-shadow: 0px 0px 12px rgba(255,255,255,0.5); + grid-area: 1/-1; + text-align: center; + line-height: 1; + } +} 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..eba2bbe567c --- /dev/null +++ b/assets/css/admin/general/_upgrade.scss @@ -0,0 +1,13 @@ +.edd-pro-upgrade, +.edd-pro-upgrade:hover{ + color: $edd-pro-upgrade; + font-weight: 600; + text-decoration: none; +} + +.button.edd-pro-upgrade, +.button.edd-pro-upgrade:hover { + background-color: $edd-pro-upgrade; + color: $white; + border-color: $edd-pro-upgrade; +} diff --git a/assets/css/admin/general/style.scss b/assets/css/admin/general/style.scss new file mode 100644 index 00000000000..16d720d379a --- /dev/null +++ b/assets/css/admin/general/style.scss @@ -0,0 +1,11 @@ +@import "admin"; +@import "../../components/admin-nav.scss"; +@import "dialog"; +@import "item-header"; +@import "item-tab-wrapper"; +@import "status-badge"; +@import "upgrade"; +@import "progress-bar"; +@import "help-tip"; +@import "elements"; +@import "grid"; diff --git a/assets/css/admin/menu.scss b/assets/css/admin/menu.scss new file mode 100644 index 00000000000..305bd16839e --- /dev/null +++ b/assets/css/admin/menu.scss @@ -0,0 +1,109 @@ +/** + * 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%; + } +} + +@media screen and (max-width: $break-mobile) { + #menu-posts-download.wp-not-current-submenu .wp-submenu { + display: none; + } +} + +/* 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: $edd-pro-upgrade !important; + color: $white !important; + font-weight: 600; +} + +.edd-admin-menu__new { + color: $warning; + vertical-align: super; + font-size: 9px; + margin-left: 3px; +} diff --git a/assets/css/admin/notifications/style.scss b/assets/css/admin/notifications/style.scss new file mode 100644 index 00000000000..247209d1c1d --- /dev/null +++ b/assets/css/admin/notifications/style.scss @@ -0,0 +1,125 @@ +@import '../../variables/variables'; + +/* 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: $warning; +} +.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..59d6a602877 --- /dev/null +++ b/assets/css/admin/onboarding/style.scss @@ -0,0 +1,869 @@ +@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, +.edd-settings-form__email 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-toggle { + justify-content: center; + } +} + +.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; + } +} + +.edd-settings-form__email { + @media screen and (min-width: $break-medium) { + .edd-form-group:not(.edd-form-group__wide) { + display: table-row; + + label, + .edd-form-group__control { + display: table-cell; + } + + label { + text-align: left; + padding: 20px 10px 20px 0; + width: 200px; + line-height: 1.3; + } + } + } +} diff --git a/assets/css/admin/orders/_customer.scss b/assets/css/admin/orders/_customer.scss new file mode 100644 index 00000000000..a6ccc607b0d --- /dev/null +++ b/assets/css/admin/orders/_customer.scss @@ -0,0 +1,3 @@ +.edd-order-customer__actions { + margin-bottom: 2em; +} 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..a0382897507 --- /dev/null +++ b/assets/css/admin/orders/style.scss @@ -0,0 +1,2 @@ +@import "customer"; +@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..2e53ba77d16 --- /dev/null +++ b/assets/css/admin/pass-handler.scss @@ -0,0 +1,87 @@ +@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: 110; + + 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; + } + + &__heading { + width: 100%; + } +} diff --git a/assets/css/admin/pointers/style.scss b/assets/css/admin/pointers/style.scss new file mode 100644 index 00000000000..365576f6fe5 --- /dev/null +++ b/assets/css/admin/pointers/style.scss @@ -0,0 +1,35 @@ +@import "../../variables/variables"; + +$pointer-blue: #2271b1; + +.edd-pointer { + &.warning { + & h3 { + background: $alert-yellow; + border-color: $wp-yellow-50; + color: $gray-900; + + &:before { + color: $alert-yellow; + } + } + } + + &.edd-has-action p:last-of-type { + margin-bottom: 2em; + } + + .edd-pointer-action { + position: absolute; + left: 15px; + bottom: 15px; + background: $pointer-blue; + border-color: $pointer-blue; + + &:hover, + &:focus { + background: darken($pointer-blue, 10%); + border-color: darken($pointer-blue, 10%); + } + } +} diff --git a/assets/css/admin/promos/_about.scss b/assets/css/admin/promos/_about.scss new file mode 100644 index 00000000000..3ad95c1942f --- /dev/null +++ b/assets/css/admin/promos/_about.scss @@ -0,0 +1,487 @@ +body.edd-about { + & div.notice { + display: none; + } + + & #edd-admin-about *, & #edd-admin-about *::before, & #edd-admin-about *::after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + & #edd-admin-about { + display: flex; + flex-direction: column; + + & .edd-admin-about-section { + box-shadow: 0 2px 5px $edd-very-light-gray; + margin: 0 20px 20px; + padding: 30px; + background: #ffffff; + border: 1px solid #dddddd; + line-height: 2; + display: flex; + flex-direction: row; + + & h2 { + font-size: 24px; + line-height: 32px; + color: $wp-gray-50; + margin: 0px; + } + + & h3 { + font-size: 16px; + line-height: 22px; + color: $wp-gray-40; + } + + & ul, p { + font-size: 14px; + } + + & p { + margin-bottom: 10px; + + &.bigger { + font-size: 18px; + } + + &.smaller { + font-size: 14px; + } + + &:last-child { + margin-bottom: 0; + } + } + + & hr { + margin: 30px 0; + } + + & figure { + margin: 0; + + & img.shadow { + width: 100%; + box-shadow: 0 2px 5px $edd-very-light-gray; + } + + & figcaption { + font-size: 14px; + color: #888888; + margin-top: 5px; + text-align: center; + line-height: initial; + } + } + + & .edd-admin-columns { + display: flex; + } + + & .column { + display: flex; + flex-direction: column; + + &--20 { + width: 20%; + } + &--40 { + width: 40%; + } + &--50 { + width: 50%; + } + &--60 { + width: 60%; + } + &--80 { + width: 80%; + } + + &.align--middle { + align-items: center; + justify-content: center; + } + + &.m { + &-l { + &-15 { + margin-left: 15px; + } + } + &-r { + &-15 { + margin-right: 15px; + } + } + } + } + + & ul { + &.list-plain { + margin-top: 0; + margin-bottom: 0; + + & li { + margin-bottom: 0; + } + } + + &.list-features { + & li { + display: flex; + align-items: center; + justify-items: left; + gap: 8px; + } + } + } + + & .dashicons-star-filled { + color: gold; + } + + & .no-margin { + margin: 0 !important; + } + + & .no-padding { + padding: 0 !important; + } + + & .centered { + text-align: center !important; + } + } + + & .edd-admin-about-section-first-form { + display: flex; + + & .edd-admin-about-section-first-form-text { + flex: 1; + padding-right: 30px; + } + + & .edd-admin-about-section-first-form-video { + + & iframe { + border: 1px solid #dddddd; + } + } + } + + & .edd-admin-about-section-hero { + display: flex; + flex-direction: column; + padding: 0; + + & .edd-admin-about-section-hero-main, & .edd-admin-about-section-hero-extra { + + & div.notice { + display: none; + } + + padding: 30px; + + & h3.call-to-action { + margin-bottom: -10px; + } + + } + + & .edd-admin-about-section-hero-main { + background-color: #fafafa; + border-bottom: 1px solid #dddddd; + + &.no-border { + border-bottom: 0; + } + + & p { + color: #666; + } + } + } + + & .edd-admin-about-section-squashed { + margin-bottom: 0; + + &:not(:last-of-type) { + border-bottom: 0; + } + } + + & .edd-admin-about-section-post { + flex-direction: row; + gap: 50px; + + & h2 { + margin-bottom: -10px; + } + + & h3 { + margin-bottom: 15px; + } + + & p { + &:last-of-type { + margin-bottom: 30px; + } + } + + & img { + max-width: 250px; + } + + & .column--20 { + width: 250px; + } + + & .column--80{ + width: calc(100% - 270px); + } + + & .button-secondary { + transition: all 0.1s ease-in-out; + &:hover, &:focus { + background-color: #2271B1; + color: $white; + } + } + } + + & #edd-admin-addons { + padding: 0 30px; + & .addons-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: space-between; + + & .addon-container { + display: flex; + padding: 10px; + flex: 1 0 33.3333%; + max-width: 33.3333%; + + & .addon-item { + display: flex; + flex-direction: column; + border: 1px solid #dddddd; + box-shadow: 0 2px 5px $edd-very-light-gray; + + & .details { + padding: 20px; + display: flex; + flex-direction: row; + background-color: #ffffff; + flex-grow: 1; + + & .leftcol { + & img { + max-width: 100px; + padding: 10px; + box-shadow: 0 2px 3px $edd-very-light-gray; + } + } + + & .rightcol { + flex-direction: column; + justify-content: left; + flex-grow: 4; + padding-left: 20px; + & h5 { + font-size: 14px; + margin-bottom: 10px; + margin-top: 0px; + } + } + } + + & .actions { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + + &.has-response { + justify-content: center; + flex-grow: 10; + } + + & .status { + & span.status-label { + font-weight: 600; + &.active { + color: $wp-green-50; + } + + &.inactive { + color: $wp-red-50; + } + + &.not-installed { + color: $wp-gray-50; + } + } + } + + & .action-button { + & .button.disabled, & .button.loading { + cursor: default; + } + } + } + } + } + } + } + + @media (max-width: $break-huge) { + & #edd-admin-addons { + & .addons-container { + & .addon-container { + display: flex; + padding: 10px; + flex: 1 0 50%; + max-width: 50%; + } + } + } + } + + @media (max-width: $break-wide) { + & .welcome-message { + flex-direction: column-reverse; + + &.column--20, .column--40, .column--50, .column--60, .column--80 { + width: 100%; + + &.m-l-15 { + margin-left: 0px; + } + + &.m-r-15 { + margin-right: 0px; + } + } + } + } + + @media (max-width: $break-large) { + & .edd-admin-about-section { + flex-direction: column; + gap: 20px; + + &.welcome-message { + flex-flow: column-reverse; + } + + & .edd-admin-columns { + flex-direction: column; + } + + &.column--20, .column--40, .column--50, .column--60, .column--80 { + display: flex; + width: 100%; + + &.m-l-15 { + margin-left: 0px; + } + + &.m-r-15 { + margin-right: 0px; + } + } + } + + & .edd-admin-about-section-first-form { + display: block !important; + + & .edd-admin-about-section-first-form-text { + flex: none; + } + + & .edd-admin-about-section-first-form-video { + padding-top: 20px; + } + } + + & .edd-admin-about-section-hero { + & .edd-admin-about-section-hero-extra { + & .edd-admin-column-50 { + float: none; + width: 100%; + } + } + } + + & .edd-admin-about-section-post { + flex-direction: column; + + & .column--20, .column--80 { + display: flex; + width: 100%; + + & img { + width: auto; + max-width: 100%; + } + + &.image { + margin: 0 auto; + align-content: center; + justify-content: center; + } + + &.content { + flex-direction: column; + justify-items: left; + } + + & .edd-admin-about-section-post-link { + font-size: 1.25rem; + display: flex; + justify-items: space-around; + justify-content: center; + + & .dashicons { + display: none; + } + } + } + } + + & #edd-admin-addons { + & .addons-container { + display: flex; + flex-direction: column; + + & .addon-container { + padding: 10px; + flex: 1 0 100%; + max-width: 100%; + + & .addon-item { + & .details { + flex-direction: row; + + & .rightcol { + padding-left: 20px; + + & .addon-name { + text-align: left; + } + } + } + } + } + } + } + } + } +} diff --git a/assets/css/admin/promos/_flyout.scss b/assets/css/admin/promos/_flyout.scss new file mode 100644 index 00000000000..ef80e259ee4 --- /dev/null +++ b/assets/css/admin/promos/_flyout.scss @@ -0,0 +1,229 @@ +#edd-flyout { + position: fixed; + z-index: 99999; + transition: all 0.2s ease-in-out; + right: 40px; + bottom: 40px; + opacity: 1; + display: flex; + flex-direction: column; + align-items: flex-end; + + @media (max-width: $break-large) { + display: none; + } + + & .edd-flyout-label { + transform: translateY(-50%); + -moz-transform: translateY(-50%); + -webkit-transform: translateY(-50%); + color: $white; + background-color: $gray-700; + font-size: 12px; + white-space: nowrap; + padding: 5px 10px; + transition: all 0.2s ease-out; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + margin-top: 20px; + opacity: 0; + transform: scale(0); + } + + & #edd-flyout-button { + border: none; + padding: 0px; + background: none; + display: flex; + flex-direction: row; + gap: 10px; + align-items: center; + + & img { + width: 54px; + height: 54px; + display: block; + border-radius: 50%; + border: 3px solid $edd-notice-blue; + overflow: hidden; + transition: all 0.2s ease-in-out; + background: $white; + } + + &:hover img { + cursor: pointer; + box-shadow: 0 3px 12px 1px $medium-gray-placeholder; + } + + & .edd-flyout-label { + opacity: 0; + transform: translateY(-50%) scale(0); + } + + &:hover .edd-flyout-label { + opacity: 1; + transform: translateY(-50%) scale(1); + } + + &.has-alert:after { + transform: scale(1); + opacity: 1; + font-family: "dashicons"; + content: "\f534"; + color: $edd-alert-red; + font-size: 16px; + height: 16px; + width: 16px; + text-decoration: none; + border-radius: 999999px; + line-height: 16px; + transition: all 0.2s ease-in-out; + background-color: $white; + position: absolute; + right: 3px; + bottom: 46px; + } + } + + & #edd-flyout-items { + display: flex; + flex-direction: column-reverse; + gap: 10px; + margin-right: 12px; + margin-bottom: 12px; + height: 0px; + + & .edd-flyout-item { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + gap: 25px; + visibility: collapse; + + & a { + text-decoration: none; + color: $white; + } + + & .edd-flyout-label, .edd-flyout-icon { + transition: all 0.2s ease-in-out; + transform: scale(0); + opacity: 0; + } + + & .edd-flyout-label { + margin-top: 0px; + + & a { + display: inline-block; + line-height: initial; + height: auto !important; + } + } + + & .edd-flyout-icon { + display: flex; + justify-content: space-around; + width: 40px; + height: 40px; + border-radius: 50%; + box-shadow: 0 3px 12px 1px $medium-gray-placeholder; + background: $edd-notice-blue 0 0 no-repeat padding-box; + + &.red { + background: $edd-alert-red 0 0 no-repeat padding-box; + } + + &.green { + background: $edd-notice-green 0 0 no-repeat padding-box; + } + + & span.dashicons:before { + color: $white; + font-size: 20px; + line-height: 40px; + vertical-align: middle; + } + } + + &:hover { + cursor: pointer; + + & .edd-flyout-icon, .edd-flyout-label { + box-shadow: 0 3px 12px 1px $medium-gray-placeholder; + } + + & .edd-flyout-icon { + background: $edd-blue-gray 0 0 no-repeat padding-box; + + &.red { + background: $edd-alert-red-hover 0 0 no-repeat padding-box; + } + + &.green { + background: $edd-notice-green-hover 0 0 no-repeat padding-box; + } + } + + & .edd-flyout-label { + background-color: $gray-800; + } + } + } + } + + &.opened { + + & #edd-flyout-items { + height: auto; + + & .edd-flyout-item { + visibility: visible; + + &.edd-flyout-item { + $elements: 4; + @for $i from 0 to $elements { + &:nth-of-type( #{ $i + 1 } ) { + & .edd-flyout-icon { + transition: transform 0.2s #{ $i * 24 }ms, background-color 0.2s; + } + + & .edd-flyout-label { + transition: transform 0.2s #{ ( $i + 1 ) * 24}ms, background-color 0.2s; + } + } + } + } + + & .edd-flyout-label, .edd-flyout-icon { + opacity: 1; + transform: scale(1); + } + } + } + + + & #edd-flyout-button { + & img { + box-shadow: 0 3px 12px 1px $medium-gray-placeholder; + + } + + & .edd-flyout-label { + opacity: 0; + } + + &.has-alert:after { + opacity: 0; + transition: scale(0); + } + } + } + + &.out { + opacity: 0; + visibility: hidden; + } +} diff --git a/assets/css/admin/promos/_overlay.scss b/assets/css/admin/promos/_overlay.scss new file mode 100644 index 00000000000..dc3babeb57c --- /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: 110; + 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 { + 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/_popup.scss b/assets/css/admin/promos/_popup.scss new file mode 100644 index 00000000000..ee62ef6a4e7 --- /dev/null +++ b/assets/css/admin/promos/_popup.scss @@ -0,0 +1,52 @@ +.edd-promo-notice__popup { + display: flex; + justify-content: center; + justify-items: center; + flex-direction: column; + margin: 2em auto; + gap: 0 1.5em; + + & h2 { + line-height: 1.6em; + margin: 0 auto; + max-width: 540px; + font-size: 1.25em; + } + + & .content { + display: inherit; + flex-direction: inherit; + justify-items: center; + text-align: center; + + .edd-promo-notice__features { + text-align: left; + display: grid; + grid-template-columns: repeat( 3, auto ); + margin: 2em auto; + gap: 0 1.5em; + flex-direction: row; + + li { + display: flex; + gap: .5em; + align-items: center; + min-width: 50%; + } + + @media screen and (max-width: $break-small) { + grid-template-columns: unset; + } + } + + & .button-primary { + padding: 4px 36px; + margin: .5em auto .5em; + max-width: 360px; + } + + &__link { + color: $gray-100; + } + } +} diff --git a/assets/css/admin/promos/style.scss b/assets/css/admin/promos/style.scss new file mode 100644 index 00000000000..cb1d7ad7bde --- /dev/null +++ b/assets/css/admin/promos/style.scss @@ -0,0 +1,102 @@ +@import '../../variables/variables'; +@import 'about'; +@import 'flyout'; + +.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:not(.edd-hidden) { + display: grid !important; +} + +#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"; +@import "popup"; 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..6645f989771 --- /dev/null +++ b/assets/css/admin/reports/_layout.scss @@ -0,0 +1,37 @@ +@media screen and (min-width: $break-small) { + #edd-reports-charts-wrap { + display: grid; + grid-template-columns: repeat(2, minmax(200px, 50%)); + grid-gap: 2em; + margin: 0 20px; + } + + .edd-reports-chart { + margin-bottom: 0; + } + + .edd-reports-chart-line, + .edd-reports-chart-bar { + grid-column: 1 / span 2; + } +} + +.edd-canvas__container { + margin: auto; + position: relative; + max-width: 100%; + max-height: 75vh; + width: calc(100% - 2px); + overflow: visible; + + &.edd-canvas__type-line, + &.edd-canvas__type-bar { + height: 300px; + } +} + +.chart-timezone { + font-size: .75rem; + color: $wp-gray-10; + grid-column: 1 / span 2; +} 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/_tooltip.scss b/assets/css/admin/reports/_tooltip.scss new file mode 100644 index 00000000000..103ddfa2594 --- /dev/null +++ b/assets/css/admin/reports/_tooltip.scss @@ -0,0 +1,25 @@ +.edd-chartjs-tooltip { + position: absolute; + background: $white !important; + border-width: 0px; + border-radius: 12px !important; + box-shadow: $edd-tool-tip-shadow !important; + color: $edd-tooltip-text !important; + width: auto; + min-width: 180px; + max-width: 250px !important; + padding: 16px !important; + text-rendering: optimizeLegibility; + text-shadow: none !important; + font-size: 13px !important; + transform: translate(-50%, 0); + pointer-events: none; + white-space: nowrap; +} + +.edd-chartjs-tooltip-key { + display: inline-block; + width: 10px; + height: 10px; + margin-right: 5px; +} diff --git a/assets/css/admin/reports/style.scss b/assets/css/admin/reports/style.scss new file mode 100644 index 00000000000..0565059827c --- /dev/null +++ b/assets/css/admin/reports/style.scss @@ -0,0 +1,12 @@ +@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"; +@import "tooltip"; diff --git a/assets/css/admin/sections/_dynamic.scss b/assets/css/admin/sections/_dynamic.scss new file mode 100644 index 00000000000..227055f9410 --- /dev/null +++ b/assets/css/admin/sections/_dynamic.scss @@ -0,0 +1,28 @@ +li.section-title--add-new { + border: unset !important; + margin-bottom: 40px; + + button { + border: 0; + border-bottom: 1px solid $gray-200; + background-color: $dark-secondary; + display: flex; + gap: 3px; + width: 100%; + padding: 8px; + justify-content: center; + + &:focus-visible { + outline: none; + box-shadow: inset 0 0 1px 1px var(--wp-admin-theme-color); + } + } +} + +.edd-spacer { + height: 40px; +} + +button.edd-section-content__remove.button.button-secondary.edd-hidden { + display: none; +} diff --git a/assets/css/admin/sections/_id.scss b/assets/css/admin/sections/_id.scss new file mode 100644 index 00000000000..a37b3fcb7d6 --- /dev/null +++ b/assets/css/admin/sections/_id.scss @@ -0,0 +1,14 @@ +.edd-section__id--badge { + 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; +} diff --git a/assets/css/admin/sections/_vertical.scss b/assets/css/admin/sections/_vertical.scss new file mode 100644 index 00000000000..3f38c77fc05 --- /dev/null +++ b/assets/css/admin/sections/_vertical.scss @@ -0,0 +1,169 @@ +.edd-vertical-sections { + overflow: visible; + display: grid; + grid-template-columns: 150px 3fr; + + .section-nav { + display: flex; + flex-direction: column; + line-height: 1em; + margin: 0 -1px 0 0; + padding: 0; + background-color: #f5f5f5; + box-sizing: border-box; + + .section-title--is-active { + .dashicons { + color: $gray-60; + } + + a { + font-weight: bold; + color: $gray-60; + background-color: $white; + border-right: none; + margin-right: -1px; + + &:after { + content: ''; + width: 1px; + height: 100%; + background: $white; + position: absolute; + right: 0; + top: 0; + bottom: 0; + z-index: 3; + } + } + } + + li { + display: block; + margin: 0; + padding: 0; + background-color: #fcfcfc; + + &.edd-hidden { + display: none; + } + + > div, + a { + display: flex; + margin: 0; + padding: 9px; + text-decoration: none; + border-bottom: 1px solid #e5e5e5; + box-shadow: none; + position: relative; + align-items: center; + gap: 6px; + color: $gray-60; + outline: 0; + transition: all .25s; + + .dashicons { + line-height: 20px; + color: $gray-60; + } + + &:hover { + box-shadow: inset 5px 0; + } + } + } + + li a:focus, + .section-title--is-active a { + box-shadow: inset 5px 0 var(--wp-admin-theme-color); + } + + .section-title__indicator { + visibility: hidden; + flex-basis: 20px; + flex-shrink: 0; + height: 20px; + + + .label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-bottom: .3em; + margin: 0 auto -.3em 0; + } + } + } + + .section-title { + &:last-of-type { + margin-bottom: 24px; + } + + &.ajax--loading { + position: relative; + + &:before { + content: ' '; + position: absolute; + width: 100%; + height: 100%; + background: rgba($white,.4); + z-index: 50; + } + } + } + + @media only screen and (max-width: $break-medium) { + grid-template-columns: 48px 1fr; + } +} + +.no-js .edd-vertical-sections.use-js { + .section-nav, + &.edd-item-header-small { + display: none; + } + + .section-content { + display: block; + } +} + +@media only screen and (max-width: $break-medium) { + + .edd-vertical-sections .section-nav { + width: 48px; + + .section-title__static, + li a, + li > button { + justify-content: center; + + .dashicons { + width: 24px; + height: 24px; + font-size: 24px; + line-height: 1; + margin: 0; + } + } + + .section-title__indicator { + visibility: visible; + display: flex; + justify-content: center; + align-items: center; + width: 24px; + height: 24px; + flex-basis: 24px; + font-weight: bold; + background-color: #e5e5e5; + border-radius: 50%; + } + } + + .section-nav li .label { + @include screen-reader-text; + } +} diff --git a/assets/css/admin/sections/style.scss b/assets/css/admin/sections/style.scss new file mode 100644 index 00000000000..bb75f6144ac --- /dev/null +++ b/assets/css/admin/sections/style.scss @@ -0,0 +1,211 @@ +@import "vertical"; +@import "dynamic"; +@import "id"; + +.edd-sections-wrap { + clear: both; + width: 100%; + + .section-wrap { + background-color: $white; + border-left: 1px solid #e5e5e5; + + .row-title { + width: 30%; + } + + .editable { + display: block; + padding: 3px; + } + + div.edit-item { + margin-left: -4px; + margin-top: -20px; + } + + .customer-address.edit-item { + margin-top: 3px; + } + + span.edit-item { + display: none; + } + + .edit-item input { + font-size: 13px; + } + + .customer-name.edit-item input { + margin-top: -5px; + } + + .edd_user_search_results { + left: -2px; + top: 18px; + } + + .edd_user_search_results ul { + width: 198px; + } + + .customer-section:not(:last-child) { + border-bottom: 1px solid #eee; + } + + .customer-section table { + margin-bottom: 20px; + } + + .section-content { + border: none; + display: grid; + gap: 20px; + padding: 20px; + + h2, + > p, + > .edd-form-group { + margin: 0; + } + } + + .avatar-wrap { + float: left; + padding-right: 10px; + text-align: center; + } + + img.avatar { + border-radius: 5px; + } + + .customer-main-wrapper { + float: left; + } + + .customer-main-wrapper input[name="customerinfo[name]"] { + font-size: 24px; + } + + .customer-address-wrapper { + float: right; + margin-top: -3px; + margin-right: 50px; + width: 202px; + } + + .info-wrapper { + min-height: 125px; + overflow: visible; + } + + .customer-address span[data-key="address"], + .customer-address span[data-key="address2"], + .customer-address span[data-key="country"] { + display: block; + } + + a.delete { + color: #ff0000; + margin-right: 5px; + text-decoration: none; + } + + .notice-container { + padding-left: 20px; + padding-right: 20px; + margin-left: -20px; + margin-right: -20px; + } + + .edd-repeatable-row-standard-fields { + border: none; + } + } +} + +@media screen and (max-width: 810px) and (min-width: 656px) { + .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; + } + + #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-sections-wrap .section-wrap { + .customer-address-wrapper { + float: none; + position: absolute; + top: 84px; + left: 165px; + max-width: 200px; + } + + .customer-main-wrapper { + float: none; + position: absolute; + left: 165px; + } + + #edd-item-stats-wrapper { + padding-left: 0; + padding-right: 0; + } + + .customer-section { + margin-bottom: 0; + } + + .widefat td.no-items, + .widefat td.column-primary, + .widefat th.column-primary { + width: 100px !important; + display: table-cell; + overflow: hidden; + text-align: left; + } + + .customer-id { + display: none; + } + } +} + +.edd-section-content__actions { + display: flex; + gap: 12px; + justify-content: flex-end; + align-items: center; + flex-wrap: wrap; + + .edd-form-group { + margin-bottom: 0 !important; + margin-right: auto; + } +} + +.section-content--is-dynamic:first-of-type .edd-section-content__remove { + display: none; +} 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/_subnav.scss b/assets/css/admin/settings/_subnav.scss new file mode 100644 index 00000000000..88f5c15f184 --- /dev/null +++ b/assets/css/admin/settings/_subnav.scss @@ -0,0 +1,52 @@ +.edd-sub-nav { + margin: 0; + display: flex; + justify-content: flex-start; + gap: 4px; + flex-wrap: wrap; + + @media screen and (max-width: $break-medium) { + justify-content: center; + } + + &__wrapper { + margin: 16px 0; + } + + li { + border: 2px solid #f0f0f1; + border-radius: 4px; + margin: 0; + + a { + color: $wp-gray-50; + display: block; + padding: 6px 14px; + text-decoration: none; + white-space: nowrap; + + &:active, + &:focus { + box-shadow: none; + } + } + + &:hover, + &:active, + &:focus { + background-color: $white; + box-shadow: none; + outline: none; + border-color: $wp-gray-20; + } + + &.current { + background-color: #d7dade; + font-weight: 600; + } + } + + &__wrapper + .notice { + margin-left: 0; + } +} diff --git a/assets/css/admin/settings/style.scss b/assets/css/admin/settings/style.scss new file mode 100644 index 00000000000..28b0f414da0 --- /dev/null +++ b/assets/css/admin/settings/style.scss @@ -0,0 +1,63 @@ +@import "../../variables/variables"; +@import 'licenses'; +@import 'subnav'; + +.edd-settings-content { + max-width: $break-huge; + + h3 { + margin: 0; + } +} + +.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..a296b55400a --- /dev/null +++ b/assets/css/admin/style.scss @@ -0,0 +1,2541 @@ +/** + * 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 "general/style"; +@import "sections/style"; +@import "downloads/style"; + +@import "discounts/style"; + +@import "forms/style"; + +@import "upgrades/v3"; + +@import "reports/style"; + +@import "orders/style"; + +@import "promos/style"; + +@import "gateways/style"; + +@import "settings/style"; + +@import "discounts/style"; + +@import "dashboard/style"; + +@import "../components/toggle-checkbox"; + +/** + * Notes: + * + * [1] Backwards compatibility for vertical tabs < 3.0 + */ + +.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 { + border-color: #a00; + color: #a00; + + &:hover { + border-color: #f00; + 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; + + &.edd-hidden { + display: none; + } +} + +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; +} + +.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-toggle { + padding: 4px 0; + } +} + +.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; + margin-right: 5px; +} + +/* =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; +} + +/* 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; + 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-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'; +} + +/* 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; +} + +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-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; +} + +h3 + .notice-wrap .notice { + margin-bottom: 1em; +} + +.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); + } +} + +/* 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; +} + +/* 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: grid; + grid-template-columns: 150px 1fr; + + &.full-width { + max-width: 100%; + grid-template-columns: unset; + } + + &:after { + content: ""; + display: block; + clear: both; + visibility: hidden; + font-size: 0; + height: 0; + } + + .edd-sections-wrap { + border: none; + } +} + +.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; +} + +.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-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; +} + +@media screen and (max-width: 810px) and (min-width: 656px) { + .customer-info .customer-name { + font-size: 16px; + } + + .widefat th { + max-width: 100% !important; + display: table-cell; + } +} + +@media screen and (max-width: 656px) { + .edd-item-info.customer-info { + position: relative; + } + + .customer-info .customer-name { + font-size: 16px; + } + + #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 +-------------------------------------------------------------- */ + +/* Content wrapper */ + +#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; +} + +#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/components/admin-nav.scss b/assets/css/components/admin-nav.scss new file mode 100644 index 00000000000..06eb7593e1d --- /dev/null +++ b/assets/css/components/admin-nav.scss @@ -0,0 +1,73 @@ +.edd-nav { + &__wrapper { + background-color: $white; + box-shadow: inset 0 -3px $edd-very-light-gray; + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 0 10px -20px; + padding: 0 20px; + position: sticky; + top: 32px; + z-index: 30; + + @media screen and (max-width: $break-medium) { + top: auto; + position: relative; + justify-content: center; + flex-wrap: wrap; + + .subtitle { + padding: 18px 12px; + } + } + } + + &__tabs { + display: flex; + flex-direction: row; + justify-content: left; + flex-wrap: wrap; + margin: 0; + gap: 8px; + + @media screen and (max-width: $break-medium) { + justify-content: center; + } + + li { + display: flex; + align-items: flex-end; + margin: 0; + + &:hover, + &:focus { + box-shadow: inset 0 -3px $wp-gray-20; + } + + &.active { + color: $edd-notice-blue; + box-shadow: inset 0 -3px $edd-notice-blue; + } + } + + a.tab { + border-bottom: none; + box-shadow: none; + outline: none; + display: block; + text-decoration: none; + color: $wp-gray-50; + padding: 18px 20px; + font-weight: 600; + font-size: 16px; + text-align: center; + white-space: nowrap; + } + } +} + +.edd-admin-page #wpbody-content > .notice:not(.inline):not(.edd-notice) { + display: none; + margin-left: 0; +} diff --git a/assets/css/components/editor-header.scss b/assets/css/components/editor-header.scss new file mode 100644 index 00000000000..80f5ec08d47 --- /dev/null +++ b/assets/css/components/editor-header.scss @@ -0,0 +1,61 @@ +.edd-editor__header { + height: auto; + border-bottom: 1px solid #e2e4e7; + z-index: 30; + position: sticky; + top: 32px; + margin-left: -20px; + background: $white; + + @media screen and (max-width: $break-medium) { + top: 0; + } + + p.submit { + margin: 0; + padding: 0; + } +} + +.edd-editor__header--actions { + padding: 4px 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.edd-editor__title, +.edd-editor__actions { + display: flex; + gap: .5em; + align-items: center; +} + +.edd-editor__actions--test { + display: flex; + gap: .5em; + + @media screen and (max-width: $break-medium) { + .edd-editor__header & { + display: none; + } + } +} + +.edd-editor__status { + display: flex; + gap: .5em; + padding-right: .5em; + + .edd-help-tip { + display: block; + cursor: unset; + margin: 0; + } +} + +@media screen and (max-width: $break-medium) { + .button.edd-back { + display: none !important; + } +} diff --git a/assets/css/components/toggle-button.scss b/assets/css/components/toggle-button.scss new file mode 100644 index 00000000000..bfd7005c740 --- /dev/null +++ b/assets/css/components/toggle-button.scss @@ -0,0 +1,70 @@ +button.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-button-toggle--active:after { + 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 { + opacity: .5; + } + + &.edd-updating { + background-color: $wp-input-border !important; // remove important when enabling buttons + + &:before { + position: absolute; + top: 3px; + @include edd-spinner( + 10px, + $wp-input-border, + $wp-alternate + ); + } + + &:after { + display: none; + } + } + + &.edd-button-toggle--active { + background-color: var(--wp-admin-theme-color) !important; // remove important when enabling buttons + + :active, + :focus { + box-shadow: 0 0 0 1px $white, 0 0 0 3px var(--wp-admin-theme-color); + } + } +} diff --git a/assets/css/components/toggle-checkbox.scss b/assets/css/components/toggle-checkbox.scss new file mode 100644 index 00000000000..b67e40bcf74 --- /dev/null +++ b/assets/css/components/toggle-checkbox.scss @@ -0,0 +1,95 @@ +.edd-toggle { + position: relative; + display: flex; + gap: 5px; + overflow: visible; + align-items: center; + + input { + position: relative; + margin: 0; + padding: 0; + width: 42px; + min-width: 42px; + height: 24px; + background-color: #ccc; + transition: background 0.2s ease; + border-radius: 34px; + box-shadow: none; + border: none; + } + + label, + .label { + margin-bottom: 0 !important; + } + + input:before { + position: absolute; + content: "" !important; + height: 18px !important; + width: 18px !important; + left: 3px; + bottom: 3px; + background-color: white !important; + transition: 0.1s transform ease; + border-radius: 50%; + z-index: 999; + } + + @media only screen and (max-width: $break-medium) { + input:checked:before { + margin: -.1875rem 0 0 -.25rem; + } + } + + input:checked { + background-color: #007cba; + background-color: var( --wp-admin-theme-color ); + } + + input:active, + input:focus { + outline: 0; + box-shadow: 0 0 0 1px #fff, 0 0 0 3px #7e8993; + } + + input:checked:active, + input: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 ); + } + + input:checked:before { + transform: translateX(22px); + } + + input[type="radio"]:checked:before { + margin: 0; + transform: translateX(18px); + } + + input:disabled { + opacity: 0.5; + } + + &.inverse { + & input { + background-color: #007cba; + background-color: var( --wp-admin-theme-color ); + transform: scaleX(-1); + + &:checked { + background-color: #ccc; + } + } + } + + &.edd-form-group__control { + margin-bottom: 0; + + fieldset & { + margin-bottom: 12px; + } + } +} 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..891e0303790 --- /dev/null +++ b/assets/css/edd-admin-chosen-rtl.min.css @@ -0,0 +1 @@ +.chosen-container{font-size:14px}.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 .spinner{margin:0;position:absolute;top:4px;left:30px}.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-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 3px 24px;width:100%!important}.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..421f51492e6 --- /dev/null +++ b/assets/css/edd-admin-chosen.min.css @@ -0,0 +1 @@ +.chosen-container{font-size:14px}.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 .spinner{margin:0;position:absolute;top:4px;right:30px}.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-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 24px 3px 10px;width:100%!important}.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-emails-rtl.min.css b/assets/css/edd-admin-emails-rtl.min.css new file mode 100644 index 00000000000..e3b7ca9f862 --- /dev/null +++ b/assets/css/edd-admin-emails-rtl.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}button.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}button.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%}button.edd-button__toggle.edd-button-toggle--active:after{transform:translateX(-16px)}button.edd-button__toggle:active,button.edd-button__toggle:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px #7e8993}button.edd-button__toggle:hover{background-color:#7e8993}button.edd-button__toggle:disabled{opacity:.5}button.edd-button__toggle.edd-updating{background-color:#7e8993!important}button.edd-button__toggle.edd-updating: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}button.edd-button__toggle.edd-updating:after{display:none}button.edd-button__toggle.edd-button-toggle--active{background-color:var(--wp-admin-theme-color)!important}button.edd-button__toggle.edd-button-toggle--active :active,button.edd-button__toggle.edd-button-toggle--active :focus{box-shadow:0 0 0 1px #fff,0 0 0 3px var(--wp-admin-theme-color)}.edd-editor__header{height:auto;border-bottom:1px solid #e2e4e7;z-index:30;position:sticky;top:32px;margin-right:-20px;background:#fff}@media screen and (max-width:782px){.edd-editor__header{top:0}}.edd-editor__header p.submit{margin:0;padding:0}.edd-editor__header--actions{padding:4px 20px;display:flex;justify-content:space-between;align-items:center}.edd-editor__actions,.edd-editor__title{display:flex;gap:.5em;align-items:center}.edd-editor__actions--test{display:flex;gap:.5em}@media screen and (max-width:782px){.edd-editor__header .edd-editor__actions--test{display:none}}.edd-editor__status{display:flex;gap:.5em;padding-left:.5em}.edd-editor__status .edd-help-tip{display:block;cursor:unset;margin:0}@media screen and (max-width:782px){.button.edd-back{display:none!important}}.edd-emails__add-new__overlay{position:absolute;left:0;top:40px;background:#fff;padding:8px;border:1px solid #c3c4c7;border-radius:4px;display:flex;flex-direction:column;gap:2px;z-index:1;align-items:stretch}.edd-emails__add-new__overlay button.button{background:none!important;border:none;color:#537994!important;text-align:right}#edd-admin-notice-emails{max-width:100%;width:800px;text-align:right;padding:2em}#edd-admin-notice-emails h2{line-height:unset;margin:0}#edd-admin-notice-emails .edd-extension-manager__body{-ms-grid-rows:unset;grid-template-rows:unset;margin-bottom:1.5em}.edd-promo-notice__ajax #edd-admin-notice-emails{width:400px}.edd-promo-notice__ajax #edd-admin-notice-emails .edd-extension-manager__body{-ms-grid-columns:80px 1fr;grid-template-columns:80px 1fr}#edd-admin-notice-emails .edd-extension-manager__card-group{grid-template-columns:repeat(auto-fill,minmax(260px,1fr));text-align:right;margin:0 auto 1em;width:100%}#edd-admin-notice-emails .edd-extension-manager__card-group .edd-extension-manager__single-card{margin:0 auto 1em}.edd-promo-notice__overlay:not(.edd-promo-notice__ajax) #edd-admin-notice-emails .edd-extension-manager__body{-ms-grid-columns:60px 1fr;grid-template-columns:60px 1fr}.edd-promo-notice__overlay:not(.edd-promo-notice__ajax) #edd-admin-notice-emails .edd-extension-manager__icon{width:58px;height:58px}.edd-promo-notice__overlay:not(.edd-promo-notice__ajax) #edd-admin-notice-emails .edd-extension-manager__icon img{width:40px}#edd-admin-notice-emails .edd-plugin__recommended{display:none}#edd-emails__add{display:flex;align-items:center;gap:8px;justify-content:space-between}.edd-list-table__item td{padding:10px}td.column-status,th.column-status{text-align:left;width:80px}td.column-status{min-height:30px}td.column-status .edd-help-tip{margin-top:2px}@media screen and (max-width:782px){td.column-status{text-align:right;width:unset;min-height:40px}}tr.no-items:not(:only-child){display:none}@media screen and (min-width:1280px){.column-primary{width:250px}.column-recipient,.column-sender{width:125px}.column-context,.column-date_modified{width:150px}}table.email_templates{margin-top:1em}.edd-emails__tablenav--top{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px}.edd-emails__tablenav--top p.search-box{margin:0}.edd-emails__tablenav--top .edd-emails__actions{margin-right:auto}.edd-list-table__name{display:flex;align-items:center;gap:6px}.edd-emails__wpsmtp{display:flex;align-items:center;gap:4px;justify-content:flex-end}.edd-emails__wpsmtp a{text-decoration:none;color:#537994}.edd-emails__wpsmtp img{width:20px;height:auto}.edd-email-editor__description{margin:2em 0}.edd-editor__header--actions{position:relative}.edd-editor__header--actions .edd-email-status-badge{position:absolute;left:0;top:53px;margin-left:20px;border:1px solid;padding:12px}.edd-editor__header--actions #submit.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-form__email{background:#fff;border:1px solid #e2e4e7;padding:2em;max-width:1440px}@media screen and (min-width:782px){.edd-form__email .edd-form-group{display:-ms-grid;display:grid;-ms-grid-columns:200px 1fr;grid-template-columns:200px 1fr;margin-bottom:2em}.edd-form__email .edd-form-group>.description{-ms-grid-column:1;grid-column-start:1;-ms-grid-column-span:2;grid-column-end:3}.edd-form__email .edd-form-group>.description,.edd-form__email .notice{margin-right:200px}}.edd-email-action-reset{border-color:#d63638!important;color:#d63638!important}.edd-email-action-reset:hover{border-color:#b60012!important;color:#b60012!important}.edd-editor__actions--test+.notice{margin-top:2em}#edd-email-logs #edd-filters{padding-right:0;padding-left:0} \ No newline at end of file diff --git a/assets/css/edd-admin-emails.min.css b/assets/css/edd-admin-emails.min.css new file mode 100644 index 00000000000..40c6ddeea7f --- /dev/null +++ b/assets/css/edd-admin-emails.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}button.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}button.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%}button.edd-button__toggle.edd-button-toggle--active:after{transform:translateX(16px)}button.edd-button__toggle:active,button.edd-button__toggle:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px #7e8993}button.edd-button__toggle:hover{background-color:#7e8993}button.edd-button__toggle:disabled{opacity:.5}button.edd-button__toggle.edd-updating{background-color:#7e8993!important}button.edd-button__toggle.edd-updating: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}button.edd-button__toggle.edd-updating:after{display:none}button.edd-button__toggle.edd-button-toggle--active{background-color:var(--wp-admin-theme-color)!important}button.edd-button__toggle.edd-button-toggle--active :active,button.edd-button__toggle.edd-button-toggle--active :focus{box-shadow:0 0 0 1px #fff,0 0 0 3px var(--wp-admin-theme-color)}.edd-editor__header{height:auto;border-bottom:1px solid #e2e4e7;z-index:30;position:sticky;top:32px;margin-left:-20px;background:#fff}@media screen and (max-width:782px){.edd-editor__header{top:0}}.edd-editor__header p.submit{margin:0;padding:0}.edd-editor__header--actions{padding:4px 20px;display:flex;justify-content:space-between;align-items:center}.edd-editor__actions,.edd-editor__title{display:flex;gap:.5em;align-items:center}.edd-editor__actions--test{display:flex;gap:.5em}@media screen and (max-width:782px){.edd-editor__header .edd-editor__actions--test{display:none}}.edd-editor__status{display:flex;gap:.5em;padding-right:.5em}.edd-editor__status .edd-help-tip{display:block;cursor:unset;margin:0}@media screen and (max-width:782px){.button.edd-back{display:none!important}}.edd-emails__add-new__overlay{position:absolute;right:0;top:40px;background:#fff;padding:8px;border:1px solid #c3c4c7;border-radius:4px;display:flex;flex-direction:column;gap:2px;z-index:1;align-items:stretch}.edd-emails__add-new__overlay button.button{background:none!important;border:none;color:#537994!important;text-align:left}#edd-admin-notice-emails{max-width:100%;width:800px;text-align:left;padding:2em}#edd-admin-notice-emails h2{line-height:unset;margin:0}#edd-admin-notice-emails .edd-extension-manager__body{-ms-grid-rows:unset;grid-template-rows:unset;margin-bottom:1.5em}.edd-promo-notice__ajax #edd-admin-notice-emails{width:400px}.edd-promo-notice__ajax #edd-admin-notice-emails .edd-extension-manager__body{-ms-grid-columns:80px 1fr;grid-template-columns:80px 1fr}#edd-admin-notice-emails .edd-extension-manager__card-group{grid-template-columns:repeat(auto-fill,minmax(260px,1fr));text-align:left;margin:0 auto 1em;width:100%}#edd-admin-notice-emails .edd-extension-manager__card-group .edd-extension-manager__single-card{margin:0 auto 1em}.edd-promo-notice__overlay:not(.edd-promo-notice__ajax) #edd-admin-notice-emails .edd-extension-manager__body{-ms-grid-columns:60px 1fr;grid-template-columns:60px 1fr}.edd-promo-notice__overlay:not(.edd-promo-notice__ajax) #edd-admin-notice-emails .edd-extension-manager__icon{width:58px;height:58px}.edd-promo-notice__overlay:not(.edd-promo-notice__ajax) #edd-admin-notice-emails .edd-extension-manager__icon img{width:40px}#edd-admin-notice-emails .edd-plugin__recommended{display:none}#edd-emails__add{display:flex;align-items:center;gap:8px;justify-content:space-between}.edd-list-table__item td{padding:10px}td.column-status,th.column-status{text-align:right;width:80px}td.column-status{min-height:30px}td.column-status .edd-help-tip{margin-top:2px}@media screen and (max-width:782px){td.column-status{text-align:left;width:unset;min-height:40px}}tr.no-items:not(:only-child){display:none}@media screen and (min-width:1280px){.column-primary{width:250px}.column-recipient,.column-sender{width:125px}.column-context,.column-date_modified{width:150px}}table.email_templates{margin-top:1em}.edd-emails__tablenav--top{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px}.edd-emails__tablenav--top p.search-box{margin:0}.edd-emails__tablenav--top .edd-emails__actions{margin-left:auto}.edd-list-table__name{display:flex;align-items:center;gap:6px}.edd-emails__wpsmtp{display:flex;align-items:center;gap:4px;justify-content:flex-end}.edd-emails__wpsmtp a{text-decoration:none;color:#537994}.edd-emails__wpsmtp img{width:20px;height:auto}.edd-email-editor__description{margin:2em 0}.edd-editor__header--actions{position:relative}.edd-editor__header--actions .edd-email-status-badge{position:absolute;right:0;top:53px;margin-right:20px;border:1px solid;padding:12px}.edd-editor__header--actions #submit.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-form__email{background:#fff;border:1px solid #e2e4e7;padding:2em;max-width:1440px}@media screen and (min-width:782px){.edd-form__email .edd-form-group{display:-ms-grid;display:grid;-ms-grid-columns:200px 1fr;grid-template-columns:200px 1fr;margin-bottom:2em}.edd-form__email .edd-form-group>.description{-ms-grid-column:1;grid-column-start:1;-ms-grid-column-span:2;grid-column-end:3}.edd-form__email .edd-form-group>.description,.edd-form__email .notice{margin-left:200px}}.edd-email-action-reset{border-color:#d63638!important;color:#d63638!important}.edd-email-action-reset:hover{border-color:#b60012!important;color:#b60012!important}.edd-editor__actions--test+.notice{margin-top:2em}#edd-email-logs #edd-filters{padding-left:0;padding-right:0} \ 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..b7a5c8244e2 --- /dev/null +++ b/assets/css/edd-admin-extension-manager-rtl.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.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 .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}}.edd-extension-manager__activate{display:flex;align-items:center;gap:.5em;min-height:30px;border:1px solid #c3c4c7;border-radius:4px;padding:3px 10px}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));text-align:right}.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:40px}p+.edd-extension-manager__card-group{margin-top:24px}}.edd-extension-manager__group{display:-ms-grid;display:grid}.edd-extension-manager__unlock{margin-top:4em}.edd-extension-manager__icon{background-color:#fff;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__card--overlay{text-align:right;padding:0}.edd-extension-manager__card--overlay .edd-extension-manager__card{padding:0}.edd-extension-manager__card--overlay .edd-extension-manager__title{line-height:unset;margin-bottom:.5em}.edd-extension-manager__card--overlay .edd-extension-manager__actions{background-color:#f9f9f9;border-top:1px solid #e0e0e0;display:flex;justify-content:flex-start;align-items:center;gap:.5em;margin:0 -2em -2em;padding:16px 24px}.edd-extension-manager__card--overlay .edd-extension-manager__actions .button{margin:0}.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-settings-wrap.has-product-education .edd-settings-content{width:100%;max-width:100%}.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;margin:0 auto}.edd-extension-manager__card--detailed-2col h3{font-size:1.5rem;margin-bottom:10px;text-align:center}.edd-extension-manager__card--detailed-2col .edd-extension-manager__body{display:-ms-grid;display:grid;-ms-grid-columns:1fr 1fr;grid-template-columns:1fr 1fr;grid-gap:1em}.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}}.edd-extension-manager__card--installer[data-filter*=recommended] .edd-extension-manager__icon{border-bottom-right-radius:0;border-bottom-left-radius:0}.edd-plugin__recommended{font-size:8px;background:#008a20;color:#fff;margin-right:-1px;margin-left:-1px;padding:6px 0;border-radius:0 0 4px 4px;line-height:1;text-align:center;width:80px} \ 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..6acf0e75acd --- /dev/null +++ b/assets/css/edd-admin-extension-manager.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.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 .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}}.edd-extension-manager__activate{display:flex;align-items:center;gap:.5em;min-height:30px;border:1px solid #c3c4c7;border-radius:4px;padding:3px 10px}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));text-align:left}.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:40px}p+.edd-extension-manager__card-group{margin-top:24px}}.edd-extension-manager__group{display:-ms-grid;display:grid}.edd-extension-manager__unlock{margin-top:4em}.edd-extension-manager__icon{background-color:#fff;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__card--overlay{text-align:left;padding:0}.edd-extension-manager__card--overlay .edd-extension-manager__card{padding:0}.edd-extension-manager__card--overlay .edd-extension-manager__title{line-height:unset;margin-bottom:.5em}.edd-extension-manager__card--overlay .edd-extension-manager__actions{background-color:#f9f9f9;border-top:1px solid #e0e0e0;display:flex;justify-content:flex-start;align-items:center;gap:.5em;margin:0 -2em -2em;padding:16px 24px}.edd-extension-manager__card--overlay .edd-extension-manager__actions .button{margin:0}.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-settings-wrap.has-product-education .edd-settings-content{width:100%;max-width:100%}.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;margin:0 auto}.edd-extension-manager__card--detailed-2col h3{font-size:1.5rem;margin-bottom:10px;text-align:center}.edd-extension-manager__card--detailed-2col .edd-extension-manager__body{display:-ms-grid;display:grid;-ms-grid-columns:1fr 1fr;grid-template-columns:1fr 1fr;grid-gap:1em}.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}}.edd-extension-manager__card--installer[data-filter*=recommended] .edd-extension-manager__icon{border-bottom-left-radius:0;border-bottom-right-radius:0}.edd-plugin__recommended{font-size:8px;background:#008a20;color:#fff;margin-left:-1px;margin-right:-1px;padding:6px 0;border-radius:0 0 4px 4px;line-height:1;text-align:center;width:80px} \ 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..b2b49e0cac6 --- /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%}@media screen and (max-width:480px){#menu-posts-download.wp-not-current-submenu .wp-submenu{display:none}}#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}.edd-admin-menu__new{color:#f18200;vertical-align:super;font-size:9px;margin-right:3px} \ 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..0444a32526b --- /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%}@media screen and (max-width:480px){#menu-posts-download.wp-not-current-submenu .wp-submenu{display:none}}#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}.edd-admin-menu__new{color:#f18200;vertical-align:super;font-size:9px;margin-left:3px} \ No newline at end of file diff --git a/assets/css/edd-admin-notifications-rtl.min.css b/assets/css/edd-admin-notifications-rtl.min.css new file mode 100644 index 00000000000..ed16c4df6fc --- /dev/null +++ b/assets/css/edd-admin-notifications-rtl.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.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} \ No newline at end of file diff --git a/assets/css/edd-admin-notifications.min.css b/assets/css/edd-admin-notifications.min.css new file mode 100644 index 00000000000..8206388f382 --- /dev/null +++ b/assets/css/edd-admin-notifications.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.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} \ 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..34f11f2a3c1 --- /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,.edd-settings-form__email 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__get-suggestions-section .edd-toggle{justify-content:center}.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}@media screen and (min-width:782px){.edd-settings-form__email .edd-form-group:not(.edd-form-group__wide){display:table-row}.edd-settings-form__email .edd-form-group:not(.edd-form-group__wide) .edd-form-group__control,.edd-settings-form__email .edd-form-group:not(.edd-form-group__wide) label{display:table-cell}.edd-settings-form__email .edd-form-group:not(.edd-form-group__wide) label{text-align:right;padding:20px 0 20px 10px;width:200px;line-height:1.3}} \ 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..b30635b96b8 --- /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,.edd-settings-form__email 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__get-suggestions-section .edd-toggle{justify-content:center}.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}@media screen and (min-width:782px){.edd-settings-form__email .edd-form-group:not(.edd-form-group__wide){display:table-row}.edd-settings-form__email .edd-form-group:not(.edd-form-group__wide) .edd-form-group__control,.edd-settings-form__email .edd-form-group:not(.edd-form-group__wide) label{display:table-cell}.edd-settings-form__email .edd-form-group:not(.edd-form-group__wide) label{text-align:left;padding:20px 10px 20px 0;width:200px;line-height:1.3}} \ 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..4e09346269b --- /dev/null +++ b/assets/css/edd-admin-pass-handler-rtl.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.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:110}.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}.edd-pass-handler__heading{width:100%} \ 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..f2bead26200 --- /dev/null +++ b/assets/css/edd-admin-pass-handler.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.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:110}.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}.edd-pass-handler__heading{width:100%} \ No newline at end of file diff --git a/assets/css/edd-admin-pointers-rtl.min.css b/assets/css/edd-admin-pointers-rtl.min.css new file mode 100644 index 00000000000..93b49b5332a --- /dev/null +++ b/assets/css/edd-admin-pointers-rtl.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.edd-pointer.warning h3{background:#f0b849;border-color:#996800;color:#1a1a1a}.edd-pointer.warning h3:before{color:#f0b849}.edd-pointer.edd-has-action p:last-of-type{margin-bottom:2em}.edd-pointer .edd-pointer-action{position:absolute;right:15px;bottom:15px;background:#2271b1;border-color:#2271b1}.edd-pointer .edd-pointer-action:focus,.edd-pointer .edd-pointer-action:hover{background:#1a5686;border-color:#1a5686} \ No newline at end of file diff --git a/assets/css/edd-admin-pointers.min.css b/assets/css/edd-admin-pointers.min.css new file mode 100644 index 00000000000..d81bf3dc2f1 --- /dev/null +++ b/assets/css/edd-admin-pointers.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.edd-pointer.warning h3{background:#f0b849;border-color:#996800;color:#1a1a1a}.edd-pointer.warning h3:before{color:#f0b849}.edd-pointer.edd-has-action p:last-of-type{margin-bottom:2em}.edd-pointer .edd-pointer-action{position:absolute;left:15px;bottom:15px;background:#2271b1;border-color:#2271b1}.edd-pointer .edd-pointer-action:focus,.edd-pointer .edd-pointer-action:hover{background:#1a5686;border-color:#1a5686} \ 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..0bdb001ad5a --- /dev/null +++ b/assets/css/edd-admin-rtl.min.css @@ -0,0 +1 @@ +@media(min-width:782px){body.edd-admin-page #wpbody-content{padding-bottom:200px}}body.edd-admin-page #wpfooter .edd-footer-promotion{text-align:center;font-weight:400;font-size:13px;line-height:16px;color:#787c82;padding:20px 0 30px;margin-bottom:20px;margin-top:20px}body.edd-admin-page #wpfooter .edd-footer-promotion p{font-weight:600}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-links,body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social{display:flex;justify-content:center;align-items:center}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-links{margin:9px 0 0}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-links span{color:#c3c4c7;padding:0 7px}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social{margin:10px 0 0;gap:10px}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social li{margin-bottom:0}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social li path{fill:#a7aaad}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social li:hover path{fill:#50575e}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social a{display:block;height:16px}.edd-nav__wrapper{background-color:#fff;box-shadow:inset 0 -3px #e8e8e8;display:flex;justify-content:space-between;align-items:center;margin:0 -20px 10px 0;padding:0 20px;position:sticky;top:32px;z-index:30}@media screen and (max-width:782px){.edd-nav__wrapper{top:auto;position:relative;justify-content:center;flex-wrap:wrap}.edd-nav__wrapper .subtitle{padding:18px 12px}}.edd-nav__tabs{display:flex;flex-direction:row;justify-content:left;flex-wrap:wrap;margin:0;gap:8px}@media screen and (max-width:782px){.edd-nav__tabs{justify-content:center}}.edd-nav__tabs li{display:flex;align-items:flex-end;margin:0}.edd-nav__tabs li:focus,.edd-nav__tabs li:hover{box-shadow:inset 0 -3px #a7aaad}.edd-nav__tabs li.active{color:#0c5d95;box-shadow:inset 0 -3px #0c5d95}.edd-nav__tabs a.tab{border-bottom:none;box-shadow:none;outline:none;display:block;text-decoration:none;color:#646970;padding:18px 20px;font-weight:600;font-size:16px;text-align:center;white-space:nowrap}.edd-admin-page #wpbody-content>.notice:not(.inline):not(.edd-notice){display:none;margin-right:0}.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-item-tab-wrapper{line-height:1em;margin:0 0 0 -1px;padding:0;background-color:#f5f5f5;box-sizing:border-box}#edd-item-tab-wrapper li{display:block;margin:0;padding:0;background-color:#fcfcfc}#edd-item-tab-wrapper li.edd-hidden{display:none}#edd-item-tab-wrapper li>.edd-item-tab-label-wrap,#edd-item-tab-wrapper li a{display:flex;margin:0;padding:9px;text-decoration:none;border-bottom:1px solid #e5e5e5;box-shadow:none;position:relative;align-items:center;gap:3px;color:#50575e}#edd-item-tab-wrapper li>.edd-item-tab-label-wrap{background-color:#fff}#edd-item-tab-wrapper li a:focus,#edd-item-tab-wrapper li a:hover{box-shadow:inset -5px 0;outline:0;transition:all .25s}#edd-item-tab-wrapper-list{margin:0}@media only screen and (max-width:782px){#edd-item-tab-wrapper{width:48px}#edd-item-tab-wrapper .edd-item-tab-label{overflow:hidden;position:absolute;top:-1000em;right:-1000em;width:1px;height:1px}}.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}.button.edd-pro-upgrade,.button.edd-pro-upgrade:hover{background-color:#1da867;color:#fff;border-color:#1da867}.edd-progress-bar{display:-ms-grid;display:grid;background:#dcdcde;border-radius:99999px;padding:2px;box-shadow:inset 0 0 1px 1px #7e8993;align-items:center}.edd-progress-bar.small{height:14px}.edd-progress-bar.medium{height:16px}.edd-progress-bar.large{height:20px;padding:4px}.edd-progress-bar>.progress{height:100%;border-top-left-radius:99999px;border-bottom-left-radius:99999px;border-top-right-radius:99999px;border-bottom-right-radius:99999px;background-color:rgba(0,186,55,.3);overflow:hidden;min-width:10%;width:0;width:var(--progress-width,0);-ms-grid-row:1;grid-area:1/-1}.edd-progress-bar>.label{color:#32373c;font-weight:400;font-size:.75rem;text-shadow:0 0 12px hsla(0,0%,100%,.5);-ms-grid-row:1;grid-area:1/-1;text-align:center;line-height:1}.edd-help-tip{cursor:help;margin-top:-2px;font-size:24px;color:#7e8993}.edd-ui-tooltip{position:absolute;background:#fff!important;border-width:0;border-radius:12px!important;box-shadow:0 8px 36px 0 rgba(29,36,40,.15)!important;color:#23282d!important;max-width:300px!important;padding:16px!important;text-rendering:optimizeLegibility;text-shadow:none!important;font-size:13px!important;z-index:9999!important}.edd-ui-tooltip .title{font-weight:700}.edd-ui-tooltip .timeline{position:relative;margin:6px 0 0;padding-right:15px}.edd-ui-tooltip .timeline li{position:relative;margin:0 0 3px}.edd-ui-tooltip .timeline li:before{content:"";position:absolute;width:4px;height:4px;right:-16px;background:transparent;border:2px solid #23282d;top:0;bottom:0;margin:auto;border-radius:100%;z-index:1}.edd-ui-tooltip .timeline li:after{content:"";width:2px;height:calc(100% - 4px);background:#23282d;position:absolute;right:-13px;top:calc(50% + 3px)}.edd-hidden,.edd-ui-tooltip .timeline li:last-child:after{display:none}.edd-hidden--required{display:none!important}.edd-clearfix:after{content:"";display:table;clear:both}.edd-fadein{visibility:visible;opacity:1;transition:opacity 1s linear}.edd-fadeout{visibility:hidden;opacity:0;transition:visibility 0s 1s,opacity 1s linear}.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}.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}@media screen and (max-width:480px){.edd-admin--has-grid{-ms-grid-columns:1fr;grid-template-columns:1fr}}.edd-vertical-sections{overflow:visible;display:-ms-grid;display:grid;-ms-grid-columns:150px 3fr;grid-template-columns:150px 3fr}.edd-vertical-sections .section-nav{display:flex;flex-direction:column;line-height:1em;margin:0 0 0 -1px;padding:0;background-color:#f5f5f5;box-sizing:border-box}.edd-vertical-sections .section-nav .section-title--is-active .dashicons{color:#50575e}.edd-vertical-sections .section-nav .section-title--is-active a{font-weight:700;color:#50575e;background-color:#fff;border-left:none;margin-left:-1px}.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-vertical-sections .section-nav li{display:block;margin:0;padding:0;background-color:#fcfcfc}.edd-vertical-sections .section-nav li.edd-hidden{display:none}.edd-vertical-sections .section-nav li>div,.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;gap:6px;color:#50575e;outline:0;transition:all .25s}.edd-vertical-sections .section-nav li>div .dashicons,.edd-vertical-sections .section-nav li a .dashicons{line-height:20px;color:#50575e}.edd-vertical-sections .section-nav li>div:hover,.edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0}.edd-vertical-sections .section-nav .section-title--is-active a,.edd-vertical-sections .section-nav li a:focus{box-shadow:inset -5px 0 var(--wp-admin-theme-color)}.edd-vertical-sections .section-nav .section-title__indicator{visibility:hidden;flex-basis:20px;flex-shrink:0;height:20px}.edd-vertical-sections .section-nav .section-title__indicator+.label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-bottom:.3em;margin:0 0 -.3em auto}.edd-vertical-sections .section-title:last-of-type{margin-bottom:24px}.edd-vertical-sections .section-title.ajax--loading{position:relative}.edd-vertical-sections .section-title.ajax--loading:before{content:" ";position:absolute;width:100%;height:100%;background:hsla(0,0%,100%,.4);z-index:50}@media only screen and (max-width:782px){.edd-vertical-sections{-ms-grid-columns:48px 1fr;grid-template-columns:48px 1fr}}.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}@media only screen and (max-width:782px){.edd-vertical-sections .section-nav{width:48px}.edd-vertical-sections .section-nav .section-title__static,.edd-vertical-sections .section-nav li>button,.edd-vertical-sections .section-nav li a{justify-content:center}.edd-vertical-sections .section-nav .section-title__static .dashicons,.edd-vertical-sections .section-nav li>button .dashicons,.edd-vertical-sections .section-nav li a .dashicons{width:24px;height:24px;font-size:24px;line-height:1;margin:0}.edd-vertical-sections .section-nav .section-title__indicator{visibility:visible;display:flex;justify-content:center;align-items:center;width:24px;height:24px;flex-basis:24px;font-weight:700;background-color:#e5e5e5;border-radius:50%}.section-nav li .label{border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}}li.section-title--add-new{border:unset!important;margin-bottom:40px}li.section-title--add-new button{border:0;border-bottom:1px solid #e0e0e0;background-color:hsla(0,0%,100%,.6);display:flex;gap:3px;width:100%;padding:8px;justify-content:center}li.section-title--add-new button:focus-visible{outline:none;box-shadow:inset 0 0 1px 1px var(--wp-admin-theme-color)}.edd-spacer{height:40px}button.edd-section-content__remove.button.button-secondary.edd-hidden{display:none}.edd-section__id--badge{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-sections-wrap{clear:both;width:100%}.edd-sections-wrap .section-wrap{background-color:#fff;border-right:1px solid #e5e5e5}.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-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 .section-content{border:none;display:-ms-grid;display:grid;gap:20px;padding:20px}.edd-sections-wrap .section-wrap .section-content>.edd-form-group,.edd-sections-wrap .section-wrap .section-content>p,.edd-sections-wrap .section-wrap .section-content h2{margin: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-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}.edd-sections-wrap .section-wrap .notice-container{padding-right:20px;padding-left:20px;margin-right:-20px;margin-left:-20px}.edd-sections-wrap .section-wrap .edd-repeatable-row-standard-fields{border:none}@media screen and (max-width:810px)and (min-width:656px){.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}#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-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}.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-section-content__actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;flex-wrap:wrap}.edd-section-content__actions .edd-form-group{margin-bottom:0!important;margin-left:auto}.section-content--is-dynamic:first-of-type .edd-section-content__remove{display:none}.edd-repeatables-wrap{display:flex;flex-direction:column;gap:16px}.edd_repeatable_row{border:1px solid #c3c4c7;border-radius:3px;margin:0;padding:0}.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_repeatable_row input[type=text].large-text{width:100%}.edd_repeatable_row .edd_repeatable_row.ui-sortable-helper .edd-repeatable-row-actions .edd-remove-row{display:none}.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-actions{color:#787c82}.edd-repeatable-row-actions a{text-decoration:none;width:auto;cursor:pointer}.edd-repeatable-row-header{clear:both;background:#f6f7f7;border-bottom:1px solid #c3c4c7;display:flex;justify-content:space-between;align-items:center;padding:0 8px}.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{margin-top:0}.edd-repeatable-row-title{font-weight:600;padding:9px 0;margin-left:auto}.edd-repeatable-row-actions{display:flex;margin-right:auto;align-items:center;gap:8px}.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;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-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-variable-prices__wrapper{display:flex;flex-direction:column}.edd__header-footer{display:flex;flex-direction:row;justify-content:flex-start;gap:.5em;padding:16px 0}.edd__header-footer .button-link{text-decoration:none}.edd__header-footer .edd-header-separator{display:block}.edd-variable-prices__rows{border-top:1px solid #c3c4c7}.edd-variable-price__row{padding:20px;border:0;border-radius:0;border-bottom:1px solid #c3c4c7;display:flex;flex-wrap:wrap;gap:16px}.edd-variable-price__row .edd-section-content__fields--standard{align-items:center;flex-grow:1;border:0}@media screen and (min-width:480px){.edd-variable-price__row .edd-section-content__fields--standard{flex-wrap:nowrap}}.edd-variable-price__row .edd-section-content__fields--standard .regular-text{width:100%;max-width:100%}.edd-variable-price__row .edd-section-content__fields--standard .edd-form-group__control{margin-bottom:0}.edd-variable-price__row .edd-section-content__fields--standard .edd-option-price{margin-right:auto}.edd-variable-price__row .edd-custom-price-option-section{display:block;padding:20px calc(2rem + 12px) 20px 20px;border-top:1px solid #c3c4c7}.edd-variable-price__row .edd-custom-price-option-section-title{display:block;font-weight:600;padding:0 0 10px}.edd-variable-price__row .edd-custom-price-option-section-content{display:flex;gap:12px}.edd-variable-price__row .toggle-custom-price-option-section{color:#787c82}.edd-variable-price__row .toggle-custom-price-option-section:hover{color:#537994}.edd-variable-price__row .edd-variable-prices__default{margin-right:calc(2rem + 12px);margin-left:auto}.edd-variable-price__row .edd-variable-prices__default input:checked{opacity:.5}.edd-variable-price__row .edd-section__actions{align-self:flex-end;margin-right:auto;display:flex;gap:8px}.edd-variable-price__id{flex-basis:2rem;font-weight:600;margin-bottom:0;text-align:left}.edd-variable-price__name{flex-grow:1}.edd-section-content__fields--custom{flex-basis:100%}.closed .edd-section-content__fields--custom{display:none}.open .edd-section-content__fields--custom{display:-ms-grid;display:grid}.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_product_files.ajax--loading{position:relative}#edd_product_files.ajax--loading:before{background:none;display:block;position:absolute;top:50%;right:50%;z-index:5;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #7e8993;border-bottom-color:#f9f9f9;border-radius:100%;content:"";width:1.25em;height:1.25em;transform:translate3d(50%,-50%,0);will-change:transform}#edd_product_files.ajax--loading:after{background-color:hsla(0,0%,100%,.75);display:block;position:absolute;top:0;right:0;width:100%;height:100%;content:" ";z-index:1}.edd-download-editor__sections{margin-top:20px}.edd-download-editor__sections.edd-sections-wrap{border:1px solid #c3c4c7}.edd-download-editor__sections .section-wrap .edd-section-content__fields--standard .edd-form-group{margin-bottom:16px}.edd-download-editor__sections .section-content .inside{margin:0!important;padding:0}body:not(.block-editor-page) #edd_product_details .inside{margin:0;padding:0}body:not(.block-editor-page) .edd-download-editor__sections{border:none;margin-top:0}.edd-buy-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}.edd__handle-actions-order{display:flex;transition:opacity .25s ease-in-out}.edd__handle-actions-order button{padding:9px;border:0;background:transparent}.edd__handle-actions-order button:disabled .dashicons{opacity:.5}.edd__handle-actions-order.edd-hidden{display:none}.edd-form-group{margin-bottom:16px}.edd-form-group:last-of-type{margin-bottom:0}.edd-form-group legend{margin-bottom:8px}.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__control--row{display:flex;align-items:center;gap:8px}.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;font-weight: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-range{display:flex;align-items:center;gap:15px}.edd-range .edd-range__slider{min-width:90px;height:2px;border-radius:10px;border:none;background:#ccc}.edd-range .edd-range__slider .ui-slider-range{background:var(--wp-admin-theme-color)}.edd-range .edd-range__slider .ui-slider-handle{height:15px;width:15px;top:-6.5px;border-radius:100%;background:var(--wp-admin-theme-color);border:none;cursor:pointer;display:inline-block;position:relative}.edd-range .edd-range__input{max-width:60px}.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}@media screen and (max-width:480px){.regular-text{width:100%}}.edd-amount-type-wrapper{display:inline-flex;align-items:center;background-color:#f9f9f9}.edd-amount-type-wrapper #edd-amount,.edd-amount-type-wrapper .edd-amount-input,.edd-amount-type-wrapper input{border-top-left-radius:0;border-bottom-left-radius:0;margin-left:-2px!important;width:unset}.edd-amount-type-wrapper #edd-amount.no-controls,.edd-amount-type-wrapper .edd-amount-input.no-controls,.edd-amount-type-wrapper input.no-controls{-moz-appearance:textfield}.edd-amount-type-wrapper #edd-amount.no-controls::-webkit-inner-spin-button,.edd-amount-type-wrapper #edd-amount.no-controls::-webkit-outer-spin-button,.edd-amount-type-wrapper .edd-amount-input.no-controls::-webkit-inner-spin-button,.edd-amount-type-wrapper .edd-amount-input.no-controls::-webkit-outer-spin-button,.edd-amount-type-wrapper input.no-controls::-webkit-inner-spin-button,.edd-amount-type-wrapper input.no-controls::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.edd-amount-type-wrapper select{border-top-right-radius:0;border-bottom-right-radius:0;width:auto!important}.edd-amount-type-wrapper select.before{border-top-right-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:0;border-top-left-radius:0}.edd-amount-type-wrapper .edd__input{width:unset}.edd-amount-type-wrapper .edd__input--left{border-top-right-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:0;border-top-left-radius:0;margin-left:0}.edd-amount-type-wrapper .edd__input--right{border-top-left-radius:4px;border-bottom-left-radius:4px;border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-2px}.edd-amount-type-wrapper #edd-amount{max-width:125px}.edd-amount-type-wrapper input:focus,.edd-amount-type-wrapper select:focus{z-index:2}.edd-amount-type-wrapper .edd-input__symbol{box-shadow:0 0 0 transparent;border-radius:4px;border:1px solid #8c8f94;background-color:#f5f5f5;text-align:center;padding:0 8px;-ms-grid-row-align:stretch;align-self:stretch;line-height:2}@media screen and (max-width:782px){.edd-amount-type-wrapper .edd-input__symbol{padding:3px 10px}}.edd-amount-type-wrapper .edd-input__symbol--prefix{border-top-left-radius:0;border-bottom-left-radius:0;margin-left:0}.edd-amount-type-wrapper .edd-input__symbol+input,.edd-amount-type-wrapper .edd-input__symbol--suffix{border-top-right-radius:0;border-bottom-right-radius:0;margin-right:-1px}.edd-amount-type-wrapper .edd-input__symbol+input{border-top-left-radius:4px;border-bottom-left-radius:4px;margin-left:inherit}#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:2em;margin:0 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-canvas__container{margin:auto;position:relative;max-width:100%;max-height:75vh;width:calc(100% - 2px);overflow:visible}.edd-canvas__container.edd-canvas__type-bar,.edd-canvas__container.edd-canvas__type-line{height:300px}.chart-timezone{font-size:.75rem;color:#c3c4c7;-ms-grid-column:1;-ms-grid-column-span:2;grid-column:1/span 2}.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-chartjs-tooltip{position:absolute;background:#fff!important;border-width:0;border-radius:12px!important;box-shadow:0 8px 36px 0 rgba(29,36,40,.15)!important;color:#23282d!important;width:auto;min-width:180px;max-width:250px!important;padding:16px!important;text-rendering:optimizeLegibility;text-shadow:none!important;font-size:13px!important;transform:translate(50%);pointer-events:none;white-space:nowrap}.edd-chartjs-tooltip-key{display:inline-block;width:10px;height:10px;margin-left:5px}.edd-order-customer__actions{margin-bottom:2em}#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,body.edd-about div.notice{display:none}body.edd-about #edd-admin-about *,body.edd-about #edd-admin-about :after,body.edd-about #edd-admin-about :before{box-sizing:border-box}body.edd-about #edd-admin-about{display:flex;flex-direction:column}body.edd-about #edd-admin-about .edd-admin-about-section{box-shadow:0 2px 5px #e8e8e8;margin:0 20px 20px;padding:30px;background:#fff;border:1px solid #ddd;line-height:2;display:flex;flex-direction:row}body.edd-about #edd-admin-about .edd-admin-about-section h2{font-size:24px;line-height:32px;color:#646970;margin:0}body.edd-about #edd-admin-about .edd-admin-about-section h3{font-size:16px;line-height:22px;color:#787c82}body.edd-about #edd-admin-about .edd-admin-about-section p,body.edd-about #edd-admin-about .edd-admin-about-section ul{font-size:14px}body.edd-about #edd-admin-about .edd-admin-about-section p{margin-bottom:10px}body.edd-about #edd-admin-about .edd-admin-about-section p.bigger{font-size:18px}body.edd-about #edd-admin-about .edd-admin-about-section p.smaller{font-size:14px}body.edd-about #edd-admin-about .edd-admin-about-section p:last-child{margin-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section hr{margin:30px 0}body.edd-about #edd-admin-about .edd-admin-about-section figure{margin:0}body.edd-about #edd-admin-about .edd-admin-about-section figure img.shadow{width:100%;box-shadow:0 2px 5px #e8e8e8}body.edd-about #edd-admin-about .edd-admin-about-section figure figcaption{font-size:14px;color:#888;margin-top:5px;text-align:center;line-height:normal}body.edd-about #edd-admin-about .edd-admin-about-section .edd-admin-columns{display:flex}body.edd-about #edd-admin-about .edd-admin-about-section .column{display:flex;flex-direction:column}body.edd-about #edd-admin-about .edd-admin-about-section .column--20{width:20%}body.edd-about #edd-admin-about .edd-admin-about-section .column--40{width:40%}body.edd-about #edd-admin-about .edd-admin-about-section .column--50{width:50%}body.edd-about #edd-admin-about .edd-admin-about-section .column--60{width:60%}body.edd-about #edd-admin-about .edd-admin-about-section .column--80{width:80%}body.edd-about #edd-admin-about .edd-admin-about-section .column.align--middle{align-items:center;justify-content:center}body.edd-about #edd-admin-about .edd-admin-about-section .column.m-l-15{margin-right:15px}body.edd-about #edd-admin-about .edd-admin-about-section .column.m-r-15{margin-left:15px}body.edd-about #edd-admin-about .edd-admin-about-section ul.list-plain{margin-top:0;margin-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section ul.list-plain li{margin-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section ul.list-features li{display:flex;align-items:center;justify-items:left;gap:8px}body.edd-about #edd-admin-about .edd-admin-about-section .dashicons-star-filled{color:gold}body.edd-about #edd-admin-about .edd-admin-about-section .no-margin{margin:0!important}body.edd-about #edd-admin-about .edd-admin-about-section .no-padding{padding:0!important}body.edd-about #edd-admin-about .edd-admin-about-section .centered{text-align:center!important}body.edd-about #edd-admin-about .edd-admin-about-section-first-form{display:flex}body.edd-about #edd-admin-about .edd-admin-about-section-first-form .edd-admin-about-section-first-form-text{flex:1;padding-left:30px}body.edd-about #edd-admin-about .edd-admin-about-section-first-form .edd-admin-about-section-first-form-video iframe{border:1px solid #ddd}body.edd-about #edd-admin-about .edd-admin-about-section-hero{display:flex;flex-direction:column;padding:0}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-extra,body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main{padding:30px}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-extra div.notice,body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main div.notice{display:none}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-extra h3.call-to-action,body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main h3.call-to-action{margin-bottom:-10px}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main{background-color:#fafafa;border-bottom:1px solid #ddd}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main.no-border{border-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main p{color:#666}body.edd-about #edd-admin-about .edd-admin-about-section-squashed{margin-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section-squashed:not(:last-of-type){border-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section-post{flex-direction:row;gap:50px}body.edd-about #edd-admin-about .edd-admin-about-section-post h2{margin-bottom:-10px}body.edd-about #edd-admin-about .edd-admin-about-section-post h3{margin-bottom:15px}body.edd-about #edd-admin-about .edd-admin-about-section-post p:last-of-type{margin-bottom:30px}body.edd-about #edd-admin-about .edd-admin-about-section-post img{max-width:250px}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20{width:250px}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80{width:calc(100% - 270px)}body.edd-about #edd-admin-about .edd-admin-about-section-post .button-secondary{transition:all .1s ease-in-out}body.edd-about #edd-admin-about .edd-admin-about-section-post .button-secondary:focus,body.edd-about #edd-admin-about .edd-admin-about-section-post .button-secondary:hover{background-color:#2271b1;color:#fff}body.edd-about #edd-admin-about #edd-admin-addons{padding:0 30px}body.edd-about #edd-admin-about #edd-admin-addons .addons-container{display:flex;flex-direction:row;flex-wrap:wrap;align-items:space-between}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container{display:flex;padding:10px;flex:1 0 33.3333%;max-width:33.3333%}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item{display:flex;flex-direction:column;border:1px solid #ddd;box-shadow:0 2px 5px #e8e8e8}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details{padding:20px;display:flex;flex-direction:row;background-color:#fff;flex-grow:1}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .leftcol img{max-width:100px;padding:10px;box-shadow:0 2px 3px #e8e8e8}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .rightcol{flex-direction:column;justify-content:left;flex-grow:4;padding-right:20px}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .rightcol h5{font-size:14px;margin-bottom:10px;margin-top:0}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding:8px 12px}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions.has-response{justify-content:center;flex-grow:10}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .status span.status-label{font-weight:600}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .status span.status-label.active{color:#008a20}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .status span.status-label.inactive{color:#d63638}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .status span.status-label.not-installed{color:#646970}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .action-button .button.disabled,body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .action-button .button.loading{cursor:default}@media(max-width:1440px){body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container{display:flex;padding:10px;flex:1 0 50%;max-width:50%}}@media(max-width:1280px){body.edd-about #edd-admin-about .welcome-message{flex-direction:column-reverse}body.edd-about #edd-admin-about .welcome-message.column--20,body.edd-about #edd-admin-about .welcome-message .column--40,body.edd-about #edd-admin-about .welcome-message .column--50,body.edd-about #edd-admin-about .welcome-message .column--60,body.edd-about #edd-admin-about .welcome-message .column--80{width:100%}body.edd-about #edd-admin-about .welcome-message.column--20.m-l-15,body.edd-about #edd-admin-about .welcome-message .column--40.m-l-15,body.edd-about #edd-admin-about .welcome-message .column--50.m-l-15,body.edd-about #edd-admin-about .welcome-message .column--60.m-l-15,body.edd-about #edd-admin-about .welcome-message .column--80.m-l-15{margin-right:0}body.edd-about #edd-admin-about .welcome-message.column--20.m-r-15,body.edd-about #edd-admin-about .welcome-message .column--40.m-r-15,body.edd-about #edd-admin-about .welcome-message .column--50.m-r-15,body.edd-about #edd-admin-about .welcome-message .column--60.m-r-15,body.edd-about #edd-admin-about .welcome-message .column--80.m-r-15{margin-left:0}}@media(max-width:960px){body.edd-about #edd-admin-about .edd-admin-about-section{flex-direction:column;gap:20px}body.edd-about #edd-admin-about .edd-admin-about-section.welcome-message{flex-flow:column-reverse}body.edd-about #edd-admin-about .edd-admin-about-section .edd-admin-columns{flex-direction:column}body.edd-about #edd-admin-about .edd-admin-about-section.column--20,body.edd-about #edd-admin-about .edd-admin-about-section .column--40,body.edd-about #edd-admin-about .edd-admin-about-section .column--50,body.edd-about #edd-admin-about .edd-admin-about-section .column--60,body.edd-about #edd-admin-about .edd-admin-about-section .column--80{display:flex;width:100%}body.edd-about #edd-admin-about .edd-admin-about-section.column--20.m-l-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--40.m-l-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--50.m-l-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--60.m-l-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--80.m-l-15{margin-right:0}body.edd-about #edd-admin-about .edd-admin-about-section.column--20.m-r-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--40.m-r-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--50.m-r-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--60.m-r-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--80.m-r-15{margin-left:0}body.edd-about #edd-admin-about .edd-admin-about-section-first-form{display:block!important}body.edd-about #edd-admin-about .edd-admin-about-section-first-form .edd-admin-about-section-first-form-text{flex:none}body.edd-about #edd-admin-about .edd-admin-about-section-first-form .edd-admin-about-section-first-form-video{padding-top:20px}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-extra .edd-admin-column-50{float:none;width:100%}body.edd-about #edd-admin-about .edd-admin-about-section-post{flex-direction:column}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80{display:flex;width:100%}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20 img,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80 img{width:auto;max-width:100%}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20.image,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80.image{margin:0 auto;align-content:center;justify-content:center}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20.content,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80.content{flex-direction:column;justify-items:left}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20 .edd-admin-about-section-post-link,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80 .edd-admin-about-section-post-link{font-size:1.25rem;display:flex;justify-items:space-around;justify-content:center}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20 .edd-admin-about-section-post-link .dashicons,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80 .edd-admin-about-section-post-link .dashicons{display:none}body.edd-about #edd-admin-about #edd-admin-addons .addons-container{display:flex;flex-direction:column}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container{padding:10px;flex:1 0 100%;max-width:100%}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details{flex-direction:row}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .rightcol{padding-right:20px}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .rightcol .addon-name{text-align:right}}#edd-flyout{position:fixed;z-index:99999;transition:all .2s ease-in-out;left:40px;bottom:40px;opacity:1;display:flex;flex-direction:column;align-items:flex-end}@media(max-width:960px){#edd-flyout{display:none}}#edd-flyout .edd-flyout-label{transform:translateY(-50%);-moz-transform:translateY(-50%);-webkit-transform:translateY(-50%);color:#fff;background-color:#757575;font-size:12px;white-space:nowrap;padding:5px 10px;transition:all .2s ease-out;border-radius:3px;-moz-border-radius:3px;-webkit-border-radius:3px;margin-top:20px;opacity:0;transform:scale(0)}#edd-flyout #edd-flyout-button{border:none;padding:0;background:none;display:flex;flex-direction:row;gap:10px;align-items:center}#edd-flyout #edd-flyout-button img{width:54px;height:54px;display:block;border-radius:50%;border:3px solid #0c5d95;overflow:hidden;transition:all .2s ease-in-out;background:#fff}#edd-flyout #edd-flyout-button:hover img{cursor:pointer;box-shadow:0 3px 12px 1px rgba(30,30,30,.55)}#edd-flyout #edd-flyout-button .edd-flyout-label{opacity:0;transform:translateY(-50%) scale(0)}#edd-flyout #edd-flyout-button:hover .edd-flyout-label{opacity:1;transform:translateY(-50%) scale(1)}#edd-flyout #edd-flyout-button.has-alert:after{transform:scale(1);opacity:1;font-family:dashicons;content:"";color:#d63638;font-size:16px;height:16px;width:16px;text-decoration:none;border-radius:999999px;line-height:16px;transition:all .2s ease-in-out;background-color:#fff;position:absolute;left:3px;bottom:46px}#edd-flyout #edd-flyout-items{display:flex;flex-direction:column-reverse;gap:10px;margin-left:12px;margin-bottom:12px;height:0}#edd-flyout #edd-flyout-items .edd-flyout-item{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;gap:25px;visibility:collapse}#edd-flyout #edd-flyout-items .edd-flyout-item a{text-decoration:none;color:#fff}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon,#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-label{transition:all .2s ease-in-out;transform:scale(0);opacity:0}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-label{margin-top:0}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-label a{display:inline-block;line-height:normal;height:auto!important}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon{display:flex;justify-content:space-around;width:40px;height:40px;border-radius:50%;box-shadow:0 3px 12px 1px rgba(30,30,30,.55);background:#0c5d95 100% 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon.red{background:#d63638 100% 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon.green{background:#1da867 100% 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon span.dashicons:before{color:#fff;font-size:20px;line-height:40px;vertical-align:middle}#edd-flyout #edd-flyout-items .edd-flyout-item:hover{cursor:pointer}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-icon,#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-label{box-shadow:0 3px 12px 1px rgba(30,30,30,.55)}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-icon{background:#35495c 100% 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-icon.red{background:#b60012 100% 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-icon.green{background:#199155 100% 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-label{background-color:#494949}#edd-flyout.opened #edd-flyout-items{height:auto}#edd-flyout.opened #edd-flyout-items .edd-flyout-item{visibility:visible}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:first-of-type .edd-flyout-icon{transition:transform .2s 0ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:first-of-type .edd-flyout-label,#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(2) .edd-flyout-icon{transition:transform .2s 24ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(2) .edd-flyout-label,#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(3) .edd-flyout-icon{transition:transform .2s 48ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(3) .edd-flyout-label,#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(4) .edd-flyout-icon{transition:transform .2s 72ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(4) .edd-flyout-label{transition:transform .2s 96ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item .edd-flyout-icon,#edd-flyout.opened #edd-flyout-items .edd-flyout-item .edd-flyout-label{opacity:1;transform:scale(1)}#edd-flyout.opened #edd-flyout-button img{box-shadow:0 3px 12px 1px rgba(30,30,30,.55)}#edd-flyout.opened #edd-flyout-button .edd-flyout-label{opacity:0}#edd-flyout.opened #edd-flyout-button.has-alert:after{opacity:0;transition:scale(0)}#edd-flyout.out{opacity:0;visibility:hidden}.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:not(.edd-hidden){display:-ms-grid!important;display:grid!important}#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:110;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{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-promo-notice__popup{display:flex;justify-content:center;justify-items:center;flex-direction:column;margin:2em auto;gap:0 1.5em}.edd-promo-notice__popup h2{line-height:1.6em;margin:0 auto;max-width:540px;font-size:1.25em}.edd-promo-notice__popup .content{display:inherit;flex-direction:inherit;justify-items:center;text-align:center}.edd-promo-notice__popup .content .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;flex-direction:row}.edd-promo-notice__popup .content .edd-promo-notice__features li{display:flex;gap:.5em;align-items:center;min-width:50%}@media screen and (max-width:600px){.edd-promo-notice__popup .content .edd-promo-notice__features{-ms-grid-columns:unset;grid-template-columns:unset}}.edd-promo-notice__popup .content .button-primary{padding:4px 36px;margin:.5em auto;max-width:360px}.edd-promo-notice__popup .content__link{color:#101517}#edd-paypal-commerce-connect-wrap.loading ul.edd-paypal-account-status li span,#edd-paypal-commerce-connect-wrap.loading ul.edd-paypal-webhook-events li span{animation:skeleton-loading 1s infinite alternate;width:250px;height:18px;display:inline-block}#edd-paypal-commerce-connect-wrap.loading .edd-paypal-connect-actions span{animation:skeleton-loading 1s infinite alternate;width:150px;height:32px;display:inline-block}.edd-paypal-account-status ul{margin-right:25px;list-style-type:none}.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}@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.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-sub-nav{margin:0;display:flex;justify-content:flex-start;gap:4px;flex-wrap:wrap}@media screen and (max-width:782px){.edd-sub-nav{justify-content:center}}.edd-sub-nav__wrapper{margin:16px 0}.edd-sub-nav li{border:2px solid #f0f0f1;border-radius:4px;margin:0}.edd-sub-nav li a{color:#646970;display:block;padding:6px 14px;text-decoration:none;white-space:nowrap}.edd-sub-nav li a:active,.edd-sub-nav li a:focus{box-shadow:none}.edd-sub-nav li:active,.edd-sub-nav li:focus,.edd-sub-nav li:hover{background-color:#fff;box-shadow:none;outline:none;border-color:#a7aaad}.edd-sub-nav li.current{background-color:#d7dade;font-weight:600}.edd-sub-nav__wrapper+.notice{margin-right:0}.edd-settings-content{max-width:1440px}.edd-settings-content h3{margin:0}.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}.wp-list-table.discounts .column-amount{width:90px}.wp-list-table.discounts th.column-use_count{width:150px}#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-code-wrapper{display:flex;align-items:stretch;gap:3px}.edd-popup-trigger{display:flex!important;align-items:center;gap:3px}@media screen and (max-width:782px){.edd-popup-trigger{margin-bottom:0!important}}@media screen and (max-width:480px){.edd-popup-trigger span:not(.dashicons){display:none}}.edd-code-generator-popup{position:absolute;z-index:99;width:250px;height:auto;margin:auto;padding:10px;transform:translate(-240px,15px);background-color:#fff;border:1px solid #8c8f94;border-radius:4px;box-shadow:0 -2px 5px 0 rgba(0,0,0,.25);box-sizing:border-box;display:none}.edd-code-generator-popup:after{content:"";width:15px;height:15px;background:#fff;position:absolute;margin:auto;transform:rotate(-45deg);z-index:-1;right:0;left:0;top:-8.5px;border-color:#8c8f94;border-style:solid;border-width:1px 1px 0 0}@media screen and (max-width:480px){.edd-code-generator-popup{transform:translateY(15px) translateX(-105px)}.edd-code-generator-popup:after{right:70%}}.edd-code-generator-popup .edd-form-group{width:100%;margin-bottom:10px;padding-bottom:10px;box-sizing:border-box;margin-top:0;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #dcdcde;height:40px}.edd-code-generator-popup .edd-form-group:last-of-type{border-bottom:0}.edd-code-generator-popup .edd-form-group label{padding:5px 0;width:60px;font-size:12px;margin-bottom:0;box-sizing:border-box}@media screen and (max-width:782px){.edd-code-generator-popup .edd-form-group label{line-height:28px}}.edd-code-generator-popup .edd-form-group input:not([type=checkbox]):not([type=radio]){width:120px!important;min-height:0;height:30px}.edd-code-generator-popup .edd-form-group input:not([type=checkbox]):not([type=radio]):not(:focus){border:1px solid #8c8f94}.edd-code-generator-popup #edd-generate-code{width:100%}@media screen and (max-width:782px){.edd-code-generator-popup #edd-generate-code:before{margin-top:8px}}.edd_dashboard_widget{display:-ms-grid;display:grid;-ms-grid-columns:(minmax(150px,1fr))[2];grid-template-columns:repeat(2,minmax(150px,1fr));grid-gap:1em}.edd_dashboard_widget>div:not(.table_left):not(.table_right){-ms-grid-column-span:2;grid-column:span 2}.edd_dashboard_widget table thead td{border-bottom:1px solid #c3c4c7;color:#777}.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{text-align:left}.edd_dashboard_widget .t{font-size:12px;padding-left:12px;color:#777;width:100%}.edd_dashboard_widget .label_heading{border-top:1px solid #c3c4c7;color:#8f8f8f;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 #c3c4c7;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:var(--wp-admin-theme-color);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}}.edd-dashboard-notice{-ms-grid-column-span:2;grid-column:span 2;padding:1px;text-align:center;margin:1em -1em -1em;background-color:#f9f9f9;border:1px solid #c3c4c7}.edd-dashboard-notice--error{background:#d63638;color:#fff}.edd-dashboard-notice--error a{color:#fff}body.dashboard_page_edd-upgrades.js .postbox .hndle{cursor:default}.edd-toggle{position:relative;display:flex;gap:5px;overflow:visible;align-items:center}.edd-toggle input{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,.edd-toggle label{margin-bottom:0!important}.edd-toggle input:before{position:absolute;content:""!important;height:18px!important;width:18px!important;right:3px;bottom:3px;background-color:#fff!important;transition:transform .1s ease;border-radius:50%;z-index:999}@media only screen and (max-width:782px){.edd-toggle input:checked:before{margin:-.1875rem -.25rem 0 0}}.edd-toggle input:checked{background-color:#007cba;background-color:var(--wp-admin-theme-color)}.edd-toggle input:active,.edd-toggle input:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px #7e8993}.edd-toggle input:checked:active,.edd-toggle input: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:checked:before{transform:translateX(-22px)}.edd-toggle input[type=radio]:checked:before{margin:0;transform:translateX(-18px)}.edd-toggle input:disabled{opacity:.5}.edd-toggle.inverse input{background-color:#007cba;background-color:var(--wp-admin-theme-color);transform:scaleX(-1)}.edd-toggle.inverse input:checked{background-color:#ccc}.edd-toggle.edd-form-group__control{margin-bottom:0}fieldset .edd-toggle.edd-form-group__control{margin-bottom:12px}.edd-notice .notice-dismiss,.edd-wrap a{text-decoration:none}.wp-core-ui .edd-delete,a.edd-delete{border-color:#a00;color:#a00}.wp-core-ui .edd-delete:hover,a.edd-delete:hover{border-color:red;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}#edd-header .edd-round.edd-hidden{display:none}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}.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.edd-toggle{padding:4px 0}.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;margin-left:5px}.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}.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;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.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}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-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}h3+.notice-wrap .notice{margin-bottom:1em}.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-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}.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:-ms-grid;display:grid;-ms-grid-columns:150px 1fr;grid-template-columns:150px 1fr}#edd-item-wrapper.full-width{max-width:100%;-ms-grid-columns:unset;grid-template-columns:unset}#edd-item-wrapper:after{content:"";display:block;clear:both;visibility:hidden;font-size:0;height:0}#edd-item-wrapper .edd-sections-wrap{border:none}.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}.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-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}@media screen and (max-width:810px)and (min-width:656px){.customer-info .customer-name{font-size:16px}.widefat th{max-width:100%!important;display:table-cell}}@media screen and (max-width:656px){.edd-item-info.customer-info{position:relative}.customer-info .customer-name{font-size:16px}#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-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}#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..48b05d453f5 --- /dev/null +++ b/assets/css/edd-admin.min.css @@ -0,0 +1 @@ +@media(min-width:782px){body.edd-admin-page #wpbody-content{padding-bottom:200px}}body.edd-admin-page #wpfooter .edd-footer-promotion{text-align:center;font-weight:400;font-size:13px;line-height:16px;color:#787c82;padding:20px 0 30px;margin-bottom:20px;margin-top:20px}body.edd-admin-page #wpfooter .edd-footer-promotion p{font-weight:600}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-links,body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social{display:flex;justify-content:center;align-items:center}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-links{margin:9px 0 0}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-links span{color:#c3c4c7;padding:0 7px}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social{margin:10px 0 0;gap:10px}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social li{margin-bottom:0}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social li path{fill:#a7aaad}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social li:hover path{fill:#50575e}body.edd-admin-page #wpfooter .edd-footer-promotion .edd-footer-promotion-social a{display:block;height:16px}.edd-nav__wrapper{background-color:#fff;box-shadow:inset 0 -3px #e8e8e8;display:flex;justify-content:space-between;align-items:center;margin:0 0 10px -20px;padding:0 20px;position:sticky;top:32px;z-index:30}@media screen and (max-width:782px){.edd-nav__wrapper{top:auto;position:relative;justify-content:center;flex-wrap:wrap}.edd-nav__wrapper .subtitle{padding:18px 12px}}.edd-nav__tabs{display:flex;flex-direction:row;justify-content:left;flex-wrap:wrap;margin:0;gap:8px}@media screen and (max-width:782px){.edd-nav__tabs{justify-content:center}}.edd-nav__tabs li{display:flex;align-items:flex-end;margin:0}.edd-nav__tabs li:focus,.edd-nav__tabs li:hover{box-shadow:inset 0 -3px #a7aaad}.edd-nav__tabs li.active{color:#0c5d95;box-shadow:inset 0 -3px #0c5d95}.edd-nav__tabs a.tab{border-bottom:none;box-shadow:none;outline:none;display:block;text-decoration:none;color:#646970;padding:18px 20px;font-weight:600;font-size:16px;text-align:center;white-space:nowrap}.edd-admin-page #wpbody-content>.notice:not(.inline):not(.edd-notice){display:none;margin-left:0}.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-item-tab-wrapper{line-height:1em;margin:0 -1px 0 0;padding:0;background-color:#f5f5f5;box-sizing:border-box}#edd-item-tab-wrapper li{display:block;margin:0;padding:0;background-color:#fcfcfc}#edd-item-tab-wrapper li.edd-hidden{display:none}#edd-item-tab-wrapper li>.edd-item-tab-label-wrap,#edd-item-tab-wrapper li a{display:flex;margin:0;padding:9px;text-decoration:none;border-bottom:1px solid #e5e5e5;box-shadow:none;position:relative;align-items:center;gap:3px;color:#50575e}#edd-item-tab-wrapper li>.edd-item-tab-label-wrap{background-color:#fff}#edd-item-tab-wrapper li a:focus,#edd-item-tab-wrapper li a:hover{box-shadow:inset 5px 0;outline:0;transition:all .25s}#edd-item-tab-wrapper-list{margin:0}@media only screen and (max-width:782px){#edd-item-tab-wrapper{width:48px}#edd-item-tab-wrapper .edd-item-tab-label{overflow:hidden;position:absolute;top:-1000em;left:-1000em;width:1px;height:1px}}.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}.button.edd-pro-upgrade,.button.edd-pro-upgrade:hover{background-color:#1da867;color:#fff;border-color:#1da867}.edd-progress-bar{display:-ms-grid;display:grid;background:#dcdcde;border-radius:99999px;padding:2px;box-shadow:inset 0 0 1px 1px #7e8993;align-items:center}.edd-progress-bar.small{height:14px}.edd-progress-bar.medium{height:16px}.edd-progress-bar.large{height:20px;padding:4px}.edd-progress-bar>.progress{height:100%;border-top-right-radius:99999px;border-bottom-right-radius:99999px;border-top-left-radius:99999px;border-bottom-left-radius:99999px;background-color:rgba(0,186,55,.3);overflow:hidden;min-width:10%;width:0;width:var(--progress-width,0);-ms-grid-row:1;grid-area:1/-1}.edd-progress-bar>.label{color:#32373c;font-weight:400;font-size:.75rem;text-shadow:0 0 12px hsla(0,0%,100%,.5);-ms-grid-row:1;grid-area:1/-1;text-align:center;line-height:1}.edd-help-tip{cursor:help;margin-top:-2px;font-size:24px;color:#7e8993}.edd-ui-tooltip{position:absolute;background:#fff!important;border-width:0;border-radius:12px!important;box-shadow:0 8px 36px 0 rgba(29,36,40,.15)!important;color:#23282d!important;max-width:300px!important;padding:16px!important;text-rendering:optimizeLegibility;text-shadow:none!important;font-size:13px!important;z-index:9999!important}.edd-ui-tooltip .title{font-weight:700}.edd-ui-tooltip .timeline{position:relative;margin:6px 0 0;padding-left:15px}.edd-ui-tooltip .timeline li{position:relative;margin:0 0 3px}.edd-ui-tooltip .timeline li:before{content:"";position:absolute;width:4px;height:4px;left:-16px;background:transparent;border:2px solid #23282d;top:0;bottom:0;margin:auto;border-radius:100%;z-index:1}.edd-ui-tooltip .timeline li:after{content:"";width:2px;height:calc(100% - 4px);background:#23282d;position:absolute;left:-13px;top:calc(50% + 3px)}.edd-hidden,.edd-ui-tooltip .timeline li:last-child:after{display:none}.edd-hidden--required{display:none!important}.edd-clearfix:after{content:"";display:table;clear:both}.edd-fadein{visibility:visible;opacity:1;transition:opacity 1s linear}.edd-fadeout{visibility:hidden;opacity:0;transition:visibility 0s 1s,opacity 1s linear}.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}.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}@media screen and (max-width:480px){.edd-admin--has-grid{-ms-grid-columns:1fr;grid-template-columns:1fr}}.edd-vertical-sections{overflow:visible;display:-ms-grid;display:grid;-ms-grid-columns:150px 3fr;grid-template-columns:150px 3fr}.edd-vertical-sections .section-nav{display:flex;flex-direction:column;line-height:1em;margin:0 -1px 0 0;padding:0;background-color:#f5f5f5;box-sizing:border-box}.edd-vertical-sections .section-nav .section-title--is-active .dashicons{color:#50575e}.edd-vertical-sections .section-nav .section-title--is-active a{font-weight:700;color:#50575e;background-color:#fff;border-right:none;margin-right:-1px}.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-vertical-sections .section-nav li{display:block;margin:0;padding:0;background-color:#fcfcfc}.edd-vertical-sections .section-nav li.edd-hidden{display:none}.edd-vertical-sections .section-nav li>div,.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;gap:6px;color:#50575e;outline:0;transition:all .25s}.edd-vertical-sections .section-nav li>div .dashicons,.edd-vertical-sections .section-nav li a .dashicons{line-height:20px;color:#50575e}.edd-vertical-sections .section-nav li>div:hover,.edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0}.edd-vertical-sections .section-nav .section-title--is-active a,.edd-vertical-sections .section-nav li a:focus{box-shadow:inset 5px 0 var(--wp-admin-theme-color)}.edd-vertical-sections .section-nav .section-title__indicator{visibility:hidden;flex-basis:20px;flex-shrink:0;height:20px}.edd-vertical-sections .section-nav .section-title__indicator+.label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-bottom:.3em;margin:0 auto -.3em 0}.edd-vertical-sections .section-title:last-of-type{margin-bottom:24px}.edd-vertical-sections .section-title.ajax--loading{position:relative}.edd-vertical-sections .section-title.ajax--loading:before{content:" ";position:absolute;width:100%;height:100%;background:hsla(0,0%,100%,.4);z-index:50}@media only screen and (max-width:782px){.edd-vertical-sections{-ms-grid-columns:48px 1fr;grid-template-columns:48px 1fr}}.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}@media only screen and (max-width:782px){.edd-vertical-sections .section-nav{width:48px}.edd-vertical-sections .section-nav .section-title__static,.edd-vertical-sections .section-nav li>button,.edd-vertical-sections .section-nav li a{justify-content:center}.edd-vertical-sections .section-nav .section-title__static .dashicons,.edd-vertical-sections .section-nav li>button .dashicons,.edd-vertical-sections .section-nav li a .dashicons{width:24px;height:24px;font-size:24px;line-height:1;margin:0}.edd-vertical-sections .section-nav .section-title__indicator{visibility:visible;display:flex;justify-content:center;align-items:center;width:24px;height:24px;flex-basis:24px;font-weight:700;background-color:#e5e5e5;border-radius:50%}.section-nav li .label{border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}}li.section-title--add-new{border:unset!important;margin-bottom:40px}li.section-title--add-new button{border:0;border-bottom:1px solid #e0e0e0;background-color:hsla(0,0%,100%,.6);display:flex;gap:3px;width:100%;padding:8px;justify-content:center}li.section-title--add-new button:focus-visible{outline:none;box-shadow:inset 0 0 1px 1px var(--wp-admin-theme-color)}.edd-spacer{height:40px}button.edd-section-content__remove.button.button-secondary.edd-hidden{display:none}.edd-section__id--badge{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-sections-wrap{clear:both;width:100%}.edd-sections-wrap .section-wrap{background-color:#fff;border-left:1px solid #e5e5e5}.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-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 .section-content{border:none;display:-ms-grid;display:grid;gap:20px;padding:20px}.edd-sections-wrap .section-wrap .section-content>.edd-form-group,.edd-sections-wrap .section-wrap .section-content>p,.edd-sections-wrap .section-wrap .section-content h2{margin: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-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}.edd-sections-wrap .section-wrap .notice-container{padding-left:20px;padding-right:20px;margin-left:-20px;margin-right:-20px}.edd-sections-wrap .section-wrap .edd-repeatable-row-standard-fields{border:none}@media screen and (max-width:810px)and (min-width:656px){.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}#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-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}.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-section-content__actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;flex-wrap:wrap}.edd-section-content__actions .edd-form-group{margin-bottom:0!important;margin-right:auto}.section-content--is-dynamic:first-of-type .edd-section-content__remove{display:none}.edd-repeatables-wrap{display:flex;flex-direction:column;gap:16px}.edd_repeatable_row{border:1px solid #c3c4c7;border-radius:3px;margin:0;padding:0}.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_repeatable_row input[type=text].large-text{width:100%}.edd_repeatable_row .edd_repeatable_row.ui-sortable-helper .edd-repeatable-row-actions .edd-remove-row{display:none}.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-actions{color:#787c82}.edd-repeatable-row-actions a{text-decoration:none;width:auto;cursor:pointer}.edd-repeatable-row-header{clear:both;background:#f6f7f7;border-bottom:1px solid #c3c4c7;display:flex;justify-content:space-between;align-items:center;padding:0 8px}.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{margin-top:0}.edd-repeatable-row-title{font-weight:600;padding:9px 0;margin-right:auto}.edd-repeatable-row-actions{display:flex;margin-left:auto;align-items:center;gap:8px}.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;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-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-variable-prices__wrapper{display:flex;flex-direction:column}.edd__header-footer{display:flex;flex-direction:row;justify-content:flex-start;gap:.5em;padding:16px 0}.edd__header-footer .button-link{text-decoration:none}.edd__header-footer .edd-header-separator{display:block}.edd-variable-prices__rows{border-top:1px solid #c3c4c7}.edd-variable-price__row{padding:20px;border:0;border-radius:0;border-bottom:1px solid #c3c4c7;display:flex;flex-wrap:wrap;gap:16px}.edd-variable-price__row .edd-section-content__fields--standard{align-items:center;flex-grow:1;border:0}@media screen and (min-width:480px){.edd-variable-price__row .edd-section-content__fields--standard{flex-wrap:nowrap}}.edd-variable-price__row .edd-section-content__fields--standard .regular-text{width:100%;max-width:100%}.edd-variable-price__row .edd-section-content__fields--standard .edd-form-group__control{margin-bottom:0}.edd-variable-price__row .edd-section-content__fields--standard .edd-option-price{margin-left:auto}.edd-variable-price__row .edd-custom-price-option-section{display:block;padding:20px 20px 20px calc(2rem + 12px);border-top:1px solid #c3c4c7}.edd-variable-price__row .edd-custom-price-option-section-title{display:block;font-weight:600;padding:0 0 10px}.edd-variable-price__row .edd-custom-price-option-section-content{display:flex;gap:12px}.edd-variable-price__row .toggle-custom-price-option-section{color:#787c82}.edd-variable-price__row .toggle-custom-price-option-section:hover{color:#537994}.edd-variable-price__row .edd-variable-prices__default{margin-left:calc(2rem + 12px);margin-right:auto}.edd-variable-price__row .edd-variable-prices__default input:checked{opacity:.5}.edd-variable-price__row .edd-section__actions{align-self:flex-end;margin-left:auto;display:flex;gap:8px}.edd-variable-price__id{flex-basis:2rem;font-weight:600;margin-bottom:0;text-align:right}.edd-variable-price__name{flex-grow:1}.edd-section-content__fields--custom{flex-basis:100%}.closed .edd-section-content__fields--custom{display:none}.open .edd-section-content__fields--custom{display:-ms-grid;display:grid}.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_product_files.ajax--loading{position:relative}#edd_product_files.ajax--loading:before{background:none;display:block;position:absolute;top:50%;left:50%;z-index:5;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #7e8993;border-bottom-color:#f9f9f9;border-radius:100%;content:"";width:1.25em;height:1.25em;transform:translate3d(-50%,-50%,0);will-change:transform}#edd_product_files.ajax--loading:after{background-color:hsla(0,0%,100%,.75);display:block;position:absolute;top:0;left:0;width:100%;height:100%;content:" ";z-index:1}.edd-download-editor__sections{margin-top:20px}.edd-download-editor__sections.edd-sections-wrap{border:1px solid #c3c4c7}.edd-download-editor__sections .section-wrap .edd-section-content__fields--standard .edd-form-group{margin-bottom:16px}.edd-download-editor__sections .section-content .inside{margin:0!important;padding:0}body:not(.block-editor-page) #edd_product_details .inside{margin:0;padding:0}body:not(.block-editor-page) .edd-download-editor__sections{border:none;margin-top:0}.edd-buy-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}.edd__handle-actions-order{display:flex;transition:opacity .25s ease-in-out}.edd__handle-actions-order button{padding:9px;border:0;background:transparent}.edd__handle-actions-order button:disabled .dashicons{opacity:.5}.edd__handle-actions-order.edd-hidden{display:none}.edd-form-group{margin-bottom:16px}.edd-form-group:last-of-type{margin-bottom:0}.edd-form-group legend{margin-bottom:8px}.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__control--row{display:flex;align-items:center;gap:8px}.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;font-weight: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-range{display:flex;align-items:center;gap:15px}.edd-range .edd-range__slider{min-width:90px;height:2px;border-radius:10px;border:none;background:#ccc}.edd-range .edd-range__slider .ui-slider-range{background:var(--wp-admin-theme-color)}.edd-range .edd-range__slider .ui-slider-handle{height:15px;width:15px;top:-6.5px;border-radius:100%;background:var(--wp-admin-theme-color);border:none;cursor:pointer;display:inline-block;position:relative}.edd-range .edd-range__input{max-width:60px}.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}@media screen and (max-width:480px){.regular-text{width:100%}}.edd-amount-type-wrapper{display:inline-flex;align-items:center;background-color:#f9f9f9}.edd-amount-type-wrapper #edd-amount,.edd-amount-type-wrapper .edd-amount-input,.edd-amount-type-wrapper input{border-top-right-radius:0;border-bottom-right-radius:0;margin-right:-2px!important;width:unset}.edd-amount-type-wrapper #edd-amount.no-controls,.edd-amount-type-wrapper .edd-amount-input.no-controls,.edd-amount-type-wrapper input.no-controls{-moz-appearance:textfield}.edd-amount-type-wrapper #edd-amount.no-controls::-webkit-inner-spin-button,.edd-amount-type-wrapper #edd-amount.no-controls::-webkit-outer-spin-button,.edd-amount-type-wrapper .edd-amount-input.no-controls::-webkit-inner-spin-button,.edd-amount-type-wrapper .edd-amount-input.no-controls::-webkit-outer-spin-button,.edd-amount-type-wrapper input.no-controls::-webkit-inner-spin-button,.edd-amount-type-wrapper input.no-controls::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.edd-amount-type-wrapper select{border-top-left-radius:0;border-bottom-left-radius:0;width:auto!important}.edd-amount-type-wrapper select.before{border-top-left-radius:4px;border-bottom-left-radius:4px;border-bottom-right-radius:0;border-top-right-radius:0}.edd-amount-type-wrapper .edd__input{width:unset}.edd-amount-type-wrapper .edd__input--left{border-top-left-radius:4px;border-bottom-left-radius:4px;border-bottom-right-radius:0;border-top-right-radius:0;margin-right:0}.edd-amount-type-wrapper .edd__input--right{border-top-right-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-2px}.edd-amount-type-wrapper #edd-amount{max-width:125px}.edd-amount-type-wrapper input:focus,.edd-amount-type-wrapper select:focus{z-index:2}.edd-amount-type-wrapper .edd-input__symbol{box-shadow:0 0 0 transparent;border-radius:4px;border:1px solid #8c8f94;background-color:#f5f5f5;text-align:center;padding:0 8px;-ms-grid-row-align:stretch;align-self:stretch;line-height:2}@media screen and (max-width:782px){.edd-amount-type-wrapper .edd-input__symbol{padding:3px 10px}}.edd-amount-type-wrapper .edd-input__symbol--prefix{border-top-right-radius:0;border-bottom-right-radius:0;margin-right:0}.edd-amount-type-wrapper .edd-input__symbol+input,.edd-amount-type-wrapper .edd-input__symbol--suffix{border-top-left-radius:0;border-bottom-left-radius:0;margin-left:-1px}.edd-amount-type-wrapper .edd-input__symbol+input{border-top-right-radius:4px;border-bottom-right-radius:4px;margin-right:inherit}#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:2em;margin:0 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-canvas__container{margin:auto;position:relative;max-width:100%;max-height:75vh;width:calc(100% - 2px);overflow:visible}.edd-canvas__container.edd-canvas__type-bar,.edd-canvas__container.edd-canvas__type-line{height:300px}.chart-timezone{font-size:.75rem;color:#c3c4c7;-ms-grid-column:1;-ms-grid-column-span:2;grid-column:1/span 2}.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-chartjs-tooltip{position:absolute;background:#fff!important;border-width:0;border-radius:12px!important;box-shadow:0 8px 36px 0 rgba(29,36,40,.15)!important;color:#23282d!important;width:auto;min-width:180px;max-width:250px!important;padding:16px!important;text-rendering:optimizeLegibility;text-shadow:none!important;font-size:13px!important;transform:translate(-50%);pointer-events:none;white-space:nowrap}.edd-chartjs-tooltip-key{display:inline-block;width:10px;height:10px;margin-right:5px}.edd-order-customer__actions{margin-bottom:2em}#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,body.edd-about div.notice{display:none}body.edd-about #edd-admin-about *,body.edd-about #edd-admin-about :after,body.edd-about #edd-admin-about :before{box-sizing:border-box}body.edd-about #edd-admin-about{display:flex;flex-direction:column}body.edd-about #edd-admin-about .edd-admin-about-section{box-shadow:0 2px 5px #e8e8e8;margin:0 20px 20px;padding:30px;background:#fff;border:1px solid #ddd;line-height:2;display:flex;flex-direction:row}body.edd-about #edd-admin-about .edd-admin-about-section h2{font-size:24px;line-height:32px;color:#646970;margin:0}body.edd-about #edd-admin-about .edd-admin-about-section h3{font-size:16px;line-height:22px;color:#787c82}body.edd-about #edd-admin-about .edd-admin-about-section p,body.edd-about #edd-admin-about .edd-admin-about-section ul{font-size:14px}body.edd-about #edd-admin-about .edd-admin-about-section p{margin-bottom:10px}body.edd-about #edd-admin-about .edd-admin-about-section p.bigger{font-size:18px}body.edd-about #edd-admin-about .edd-admin-about-section p.smaller{font-size:14px}body.edd-about #edd-admin-about .edd-admin-about-section p:last-child{margin-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section hr{margin:30px 0}body.edd-about #edd-admin-about .edd-admin-about-section figure{margin:0}body.edd-about #edd-admin-about .edd-admin-about-section figure img.shadow{width:100%;box-shadow:0 2px 5px #e8e8e8}body.edd-about #edd-admin-about .edd-admin-about-section figure figcaption{font-size:14px;color:#888;margin-top:5px;text-align:center;line-height:normal}body.edd-about #edd-admin-about .edd-admin-about-section .edd-admin-columns{display:flex}body.edd-about #edd-admin-about .edd-admin-about-section .column{display:flex;flex-direction:column}body.edd-about #edd-admin-about .edd-admin-about-section .column--20{width:20%}body.edd-about #edd-admin-about .edd-admin-about-section .column--40{width:40%}body.edd-about #edd-admin-about .edd-admin-about-section .column--50{width:50%}body.edd-about #edd-admin-about .edd-admin-about-section .column--60{width:60%}body.edd-about #edd-admin-about .edd-admin-about-section .column--80{width:80%}body.edd-about #edd-admin-about .edd-admin-about-section .column.align--middle{align-items:center;justify-content:center}body.edd-about #edd-admin-about .edd-admin-about-section .column.m-l-15{margin-left:15px}body.edd-about #edd-admin-about .edd-admin-about-section .column.m-r-15{margin-right:15px}body.edd-about #edd-admin-about .edd-admin-about-section ul.list-plain{margin-top:0;margin-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section ul.list-plain li{margin-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section ul.list-features li{display:flex;align-items:center;justify-items:left;gap:8px}body.edd-about #edd-admin-about .edd-admin-about-section .dashicons-star-filled{color:gold}body.edd-about #edd-admin-about .edd-admin-about-section .no-margin{margin:0!important}body.edd-about #edd-admin-about .edd-admin-about-section .no-padding{padding:0!important}body.edd-about #edd-admin-about .edd-admin-about-section .centered{text-align:center!important}body.edd-about #edd-admin-about .edd-admin-about-section-first-form{display:flex}body.edd-about #edd-admin-about .edd-admin-about-section-first-form .edd-admin-about-section-first-form-text{flex:1;padding-right:30px}body.edd-about #edd-admin-about .edd-admin-about-section-first-form .edd-admin-about-section-first-form-video iframe{border:1px solid #ddd}body.edd-about #edd-admin-about .edd-admin-about-section-hero{display:flex;flex-direction:column;padding:0}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-extra,body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main{padding:30px}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-extra div.notice,body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main div.notice{display:none}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-extra h3.call-to-action,body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main h3.call-to-action{margin-bottom:-10px}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main{background-color:#fafafa;border-bottom:1px solid #ddd}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main.no-border{border-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-main p{color:#666}body.edd-about #edd-admin-about .edd-admin-about-section-squashed{margin-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section-squashed:not(:last-of-type){border-bottom:0}body.edd-about #edd-admin-about .edd-admin-about-section-post{flex-direction:row;gap:50px}body.edd-about #edd-admin-about .edd-admin-about-section-post h2{margin-bottom:-10px}body.edd-about #edd-admin-about .edd-admin-about-section-post h3{margin-bottom:15px}body.edd-about #edd-admin-about .edd-admin-about-section-post p:last-of-type{margin-bottom:30px}body.edd-about #edd-admin-about .edd-admin-about-section-post img{max-width:250px}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20{width:250px}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80{width:calc(100% - 270px)}body.edd-about #edd-admin-about .edd-admin-about-section-post .button-secondary{transition:all .1s ease-in-out}body.edd-about #edd-admin-about .edd-admin-about-section-post .button-secondary:focus,body.edd-about #edd-admin-about .edd-admin-about-section-post .button-secondary:hover{background-color:#2271b1;color:#fff}body.edd-about #edd-admin-about #edd-admin-addons{padding:0 30px}body.edd-about #edd-admin-about #edd-admin-addons .addons-container{display:flex;flex-direction:row;flex-wrap:wrap;align-items:space-between}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container{display:flex;padding:10px;flex:1 0 33.3333%;max-width:33.3333%}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item{display:flex;flex-direction:column;border:1px solid #ddd;box-shadow:0 2px 5px #e8e8e8}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details{padding:20px;display:flex;flex-direction:row;background-color:#fff;flex-grow:1}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .leftcol img{max-width:100px;padding:10px;box-shadow:0 2px 3px #e8e8e8}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .rightcol{flex-direction:column;justify-content:left;flex-grow:4;padding-left:20px}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .rightcol h5{font-size:14px;margin-bottom:10px;margin-top:0}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding:8px 12px}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions.has-response{justify-content:center;flex-grow:10}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .status span.status-label{font-weight:600}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .status span.status-label.active{color:#008a20}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .status span.status-label.inactive{color:#d63638}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .status span.status-label.not-installed{color:#646970}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .action-button .button.disabled,body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .actions .action-button .button.loading{cursor:default}@media(max-width:1440px){body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container{display:flex;padding:10px;flex:1 0 50%;max-width:50%}}@media(max-width:1280px){body.edd-about #edd-admin-about .welcome-message{flex-direction:column-reverse}body.edd-about #edd-admin-about .welcome-message.column--20,body.edd-about #edd-admin-about .welcome-message .column--40,body.edd-about #edd-admin-about .welcome-message .column--50,body.edd-about #edd-admin-about .welcome-message .column--60,body.edd-about #edd-admin-about .welcome-message .column--80{width:100%}body.edd-about #edd-admin-about .welcome-message.column--20.m-l-15,body.edd-about #edd-admin-about .welcome-message .column--40.m-l-15,body.edd-about #edd-admin-about .welcome-message .column--50.m-l-15,body.edd-about #edd-admin-about .welcome-message .column--60.m-l-15,body.edd-about #edd-admin-about .welcome-message .column--80.m-l-15{margin-left:0}body.edd-about #edd-admin-about .welcome-message.column--20.m-r-15,body.edd-about #edd-admin-about .welcome-message .column--40.m-r-15,body.edd-about #edd-admin-about .welcome-message .column--50.m-r-15,body.edd-about #edd-admin-about .welcome-message .column--60.m-r-15,body.edd-about #edd-admin-about .welcome-message .column--80.m-r-15{margin-right:0}}@media(max-width:960px){body.edd-about #edd-admin-about .edd-admin-about-section{flex-direction:column;gap:20px}body.edd-about #edd-admin-about .edd-admin-about-section.welcome-message{flex-flow:column-reverse}body.edd-about #edd-admin-about .edd-admin-about-section .edd-admin-columns{flex-direction:column}body.edd-about #edd-admin-about .edd-admin-about-section.column--20,body.edd-about #edd-admin-about .edd-admin-about-section .column--40,body.edd-about #edd-admin-about .edd-admin-about-section .column--50,body.edd-about #edd-admin-about .edd-admin-about-section .column--60,body.edd-about #edd-admin-about .edd-admin-about-section .column--80{display:flex;width:100%}body.edd-about #edd-admin-about .edd-admin-about-section.column--20.m-l-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--40.m-l-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--50.m-l-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--60.m-l-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--80.m-l-15{margin-left:0}body.edd-about #edd-admin-about .edd-admin-about-section.column--20.m-r-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--40.m-r-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--50.m-r-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--60.m-r-15,body.edd-about #edd-admin-about .edd-admin-about-section .column--80.m-r-15{margin-right:0}body.edd-about #edd-admin-about .edd-admin-about-section-first-form{display:block!important}body.edd-about #edd-admin-about .edd-admin-about-section-first-form .edd-admin-about-section-first-form-text{flex:none}body.edd-about #edd-admin-about .edd-admin-about-section-first-form .edd-admin-about-section-first-form-video{padding-top:20px}body.edd-about #edd-admin-about .edd-admin-about-section-hero .edd-admin-about-section-hero-extra .edd-admin-column-50{float:none;width:100%}body.edd-about #edd-admin-about .edd-admin-about-section-post{flex-direction:column}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80{display:flex;width:100%}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20 img,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80 img{width:auto;max-width:100%}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20.image,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80.image{margin:0 auto;align-content:center;justify-content:center}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20.content,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80.content{flex-direction:column;justify-items:left}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20 .edd-admin-about-section-post-link,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80 .edd-admin-about-section-post-link{font-size:1.25rem;display:flex;justify-items:space-around;justify-content:center}body.edd-about #edd-admin-about .edd-admin-about-section-post .column--20 .edd-admin-about-section-post-link .dashicons,body.edd-about #edd-admin-about .edd-admin-about-section-post .column--80 .edd-admin-about-section-post-link .dashicons{display:none}body.edd-about #edd-admin-about #edd-admin-addons .addons-container{display:flex;flex-direction:column}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container{padding:10px;flex:1 0 100%;max-width:100%}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details{flex-direction:row}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .rightcol{padding-left:20px}body.edd-about #edd-admin-about #edd-admin-addons .addons-container .addon-container .addon-item .details .rightcol .addon-name{text-align:left}}#edd-flyout{position:fixed;z-index:99999;transition:all .2s ease-in-out;right:40px;bottom:40px;opacity:1;display:flex;flex-direction:column;align-items:flex-end}@media(max-width:960px){#edd-flyout{display:none}}#edd-flyout .edd-flyout-label{transform:translateY(-50%);-moz-transform:translateY(-50%);-webkit-transform:translateY(-50%);color:#fff;background-color:#757575;font-size:12px;white-space:nowrap;padding:5px 10px;transition:all .2s ease-out;border-radius:3px;-moz-border-radius:3px;-webkit-border-radius:3px;margin-top:20px;opacity:0;transform:scale(0)}#edd-flyout #edd-flyout-button{border:none;padding:0;background:none;display:flex;flex-direction:row;gap:10px;align-items:center}#edd-flyout #edd-flyout-button img{width:54px;height:54px;display:block;border-radius:50%;border:3px solid #0c5d95;overflow:hidden;transition:all .2s ease-in-out;background:#fff}#edd-flyout #edd-flyout-button:hover img{cursor:pointer;box-shadow:0 3px 12px 1px rgba(30,30,30,.55)}#edd-flyout #edd-flyout-button .edd-flyout-label{opacity:0;transform:translateY(-50%) scale(0)}#edd-flyout #edd-flyout-button:hover .edd-flyout-label{opacity:1;transform:translateY(-50%) scale(1)}#edd-flyout #edd-flyout-button.has-alert:after{transform:scale(1);opacity:1;font-family:dashicons;content:"";color:#d63638;font-size:16px;height:16px;width:16px;text-decoration:none;border-radius:999999px;line-height:16px;transition:all .2s ease-in-out;background-color:#fff;position:absolute;right:3px;bottom:46px}#edd-flyout #edd-flyout-items{display:flex;flex-direction:column-reverse;gap:10px;margin-right:12px;margin-bottom:12px;height:0}#edd-flyout #edd-flyout-items .edd-flyout-item{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;gap:25px;visibility:collapse}#edd-flyout #edd-flyout-items .edd-flyout-item a{text-decoration:none;color:#fff}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon,#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-label{transition:all .2s ease-in-out;transform:scale(0);opacity:0}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-label{margin-top:0}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-label a{display:inline-block;line-height:normal;height:auto!important}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon{display:flex;justify-content:space-around;width:40px;height:40px;border-radius:50%;box-shadow:0 3px 12px 1px rgba(30,30,30,.55);background:#0c5d95 0 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon.red{background:#d63638 0 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon.green{background:#1da867 0 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item .edd-flyout-icon span.dashicons:before{color:#fff;font-size:20px;line-height:40px;vertical-align:middle}#edd-flyout #edd-flyout-items .edd-flyout-item:hover{cursor:pointer}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-icon,#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-label{box-shadow:0 3px 12px 1px rgba(30,30,30,.55)}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-icon{background:#35495c 0 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-icon.red{background:#b60012 0 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-icon.green{background:#199155 0 0 no-repeat padding-box}#edd-flyout #edd-flyout-items .edd-flyout-item:hover .edd-flyout-label{background-color:#494949}#edd-flyout.opened #edd-flyout-items{height:auto}#edd-flyout.opened #edd-flyout-items .edd-flyout-item{visibility:visible}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:first-of-type .edd-flyout-icon{transition:transform .2s 0ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:first-of-type .edd-flyout-label,#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(2) .edd-flyout-icon{transition:transform .2s 24ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(2) .edd-flyout-label,#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(3) .edd-flyout-icon{transition:transform .2s 48ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(3) .edd-flyout-label,#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(4) .edd-flyout-icon{transition:transform .2s 72ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item.edd-flyout-item:nth-of-type(4) .edd-flyout-label{transition:transform .2s 96ms,background-color .2s}#edd-flyout.opened #edd-flyout-items .edd-flyout-item .edd-flyout-icon,#edd-flyout.opened #edd-flyout-items .edd-flyout-item .edd-flyout-label{opacity:1;transform:scale(1)}#edd-flyout.opened #edd-flyout-button img{box-shadow:0 3px 12px 1px rgba(30,30,30,.55)}#edd-flyout.opened #edd-flyout-button .edd-flyout-label{opacity:0}#edd-flyout.opened #edd-flyout-button.has-alert:after{opacity:0;transition:scale(0)}#edd-flyout.out{opacity:0;visibility:hidden}.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:not(.edd-hidden){display:-ms-grid!important;display:grid!important}#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:110;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{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-promo-notice__popup{display:flex;justify-content:center;justify-items:center;flex-direction:column;margin:2em auto;gap:0 1.5em}.edd-promo-notice__popup h2{line-height:1.6em;margin:0 auto;max-width:540px;font-size:1.25em}.edd-promo-notice__popup .content{display:inherit;flex-direction:inherit;justify-items:center;text-align:center}.edd-promo-notice__popup .content .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;flex-direction:row}.edd-promo-notice__popup .content .edd-promo-notice__features li{display:flex;gap:.5em;align-items:center;min-width:50%}@media screen and (max-width:600px){.edd-promo-notice__popup .content .edd-promo-notice__features{-ms-grid-columns:unset;grid-template-columns:unset}}.edd-promo-notice__popup .content .button-primary{padding:4px 36px;margin:.5em auto;max-width:360px}.edd-promo-notice__popup .content__link{color:#101517}#edd-paypal-commerce-connect-wrap.loading ul.edd-paypal-account-status li span,#edd-paypal-commerce-connect-wrap.loading ul.edd-paypal-webhook-events li span{animation:skeleton-loading 1s infinite alternate;width:250px;height:18px;display:inline-block}#edd-paypal-commerce-connect-wrap.loading .edd-paypal-connect-actions span{animation:skeleton-loading 1s infinite alternate;width:150px;height:32px;display:inline-block}.edd-paypal-account-status ul{margin-left:25px;list-style-type:none}.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}@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.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-sub-nav{margin:0;display:flex;justify-content:flex-start;gap:4px;flex-wrap:wrap}@media screen and (max-width:782px){.edd-sub-nav{justify-content:center}}.edd-sub-nav__wrapper{margin:16px 0}.edd-sub-nav li{border:2px solid #f0f0f1;border-radius:4px;margin:0}.edd-sub-nav li a{color:#646970;display:block;padding:6px 14px;text-decoration:none;white-space:nowrap}.edd-sub-nav li a:active,.edd-sub-nav li a:focus{box-shadow:none}.edd-sub-nav li:active,.edd-sub-nav li:focus,.edd-sub-nav li:hover{background-color:#fff;box-shadow:none;outline:none;border-color:#a7aaad}.edd-sub-nav li.current{background-color:#d7dade;font-weight:600}.edd-sub-nav__wrapper+.notice{margin-left:0}.edd-settings-content{max-width:1440px}.edd-settings-content h3{margin:0}.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}.wp-list-table.discounts .column-amount{width:90px}.wp-list-table.discounts th.column-use_count{width:150px}#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-code-wrapper{display:flex;align-items:stretch;gap:3px}.edd-popup-trigger{display:flex!important;align-items:center;gap:3px}@media screen and (max-width:782px){.edd-popup-trigger{margin-bottom:0!important}}@media screen and (max-width:480px){.edd-popup-trigger span:not(.dashicons){display:none}}.edd-code-generator-popup{position:absolute;z-index:99;width:250px;height:auto;margin:auto;padding:10px;transform:translate(240px,15px);background-color:#fff;border:1px solid #8c8f94;border-radius:4px;box-shadow:0 -2px 5px 0 rgba(0,0,0,.25);box-sizing:border-box;display:none}.edd-code-generator-popup:after{content:"";width:15px;height:15px;background:#fff;position:absolute;margin:auto;transform:rotate(45deg);z-index:-1;left:0;right:0;top:-8.5px;border-color:#8c8f94;border-style:solid;border-width:1px 0 0 1px}@media screen and (max-width:480px){.edd-code-generator-popup{transform:translateY(15px) translateX(105px)}.edd-code-generator-popup:after{left:70%}}.edd-code-generator-popup .edd-form-group{width:100%;margin-bottom:10px;padding-bottom:10px;box-sizing:border-box;margin-top:0;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #dcdcde;height:40px}.edd-code-generator-popup .edd-form-group:last-of-type{border-bottom:0}.edd-code-generator-popup .edd-form-group label{padding:5px 0;width:60px;font-size:12px;margin-bottom:0;box-sizing:border-box}@media screen and (max-width:782px){.edd-code-generator-popup .edd-form-group label{line-height:28px}}.edd-code-generator-popup .edd-form-group input:not([type=checkbox]):not([type=radio]){width:120px!important;min-height:0;height:30px}.edd-code-generator-popup .edd-form-group input:not([type=checkbox]):not([type=radio]):not(:focus){border:1px solid #8c8f94}.edd-code-generator-popup #edd-generate-code{width:100%}@media screen and (max-width:782px){.edd-code-generator-popup #edd-generate-code:before{margin-top:8px}}.edd_dashboard_widget{display:-ms-grid;display:grid;-ms-grid-columns:(minmax(150px,1fr))[2];grid-template-columns:repeat(2,minmax(150px,1fr));grid-gap:1em}.edd_dashboard_widget>div:not(.table_left):not(.table_right){-ms-grid-column-span:2;grid-column:span 2}.edd_dashboard_widget table thead td{border-bottom:1px solid #c3c4c7;color:#777}.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{text-align:right}.edd_dashboard_widget .t{font-size:12px;padding-right:12px;color:#777;width:100%}.edd_dashboard_widget .label_heading{border-top:1px solid #c3c4c7;color:#8f8f8f;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 #c3c4c7;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:var(--wp-admin-theme-color);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}}.edd-dashboard-notice{-ms-grid-column-span:2;grid-column:span 2;padding:1px;text-align:center;margin:1em -1em -1em;background-color:#f9f9f9;border:1px solid #c3c4c7}.edd-dashboard-notice--error{background:#d63638;color:#fff}.edd-dashboard-notice--error a{color:#fff}body.dashboard_page_edd-upgrades.js .postbox .hndle{cursor:default}.edd-toggle{position:relative;display:flex;gap:5px;overflow:visible;align-items:center}.edd-toggle input{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,.edd-toggle label{margin-bottom:0!important}.edd-toggle input:before{position:absolute;content:""!important;height:18px!important;width:18px!important;left:3px;bottom:3px;background-color:#fff!important;transition:transform .1s ease;border-radius:50%;z-index:999}@media only screen and (max-width:782px){.edd-toggle input:checked:before{margin:-.1875rem 0 0 -.25rem}}.edd-toggle input:checked{background-color:#007cba;background-color:var(--wp-admin-theme-color)}.edd-toggle input:active,.edd-toggle input:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px #7e8993}.edd-toggle input:checked:active,.edd-toggle input: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:checked:before{transform:translateX(22px)}.edd-toggle input[type=radio]:checked:before{margin:0;transform:translateX(18px)}.edd-toggle input:disabled{opacity:.5}.edd-toggle.inverse input{background-color:#007cba;background-color:var(--wp-admin-theme-color);transform:scaleX(-1)}.edd-toggle.inverse input:checked{background-color:#ccc}.edd-toggle.edd-form-group__control{margin-bottom:0}fieldset .edd-toggle.edd-form-group__control{margin-bottom:12px}.edd-notice .notice-dismiss,.edd-wrap a{text-decoration:none}.wp-core-ui .edd-delete,a.edd-delete{border-color:#a00;color:#a00}.wp-core-ui .edd-delete:hover,a.edd-delete:hover{border-color:red;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}#edd-header .edd-round.edd-hidden{display:none}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}.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.edd-toggle{padding:4px 0}.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;margin-right:5px}.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}.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;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.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}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-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}h3+.notice-wrap .notice{margin-bottom:1em}.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-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}.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:-ms-grid;display:grid;-ms-grid-columns:150px 1fr;grid-template-columns:150px 1fr}#edd-item-wrapper.full-width{max-width:100%;-ms-grid-columns:unset;grid-template-columns:unset}#edd-item-wrapper:after{content:"";display:block;clear:both;visibility:hidden;font-size:0;height:0}#edd-item-wrapper .edd-sections-wrap{border:none}.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}.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-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}@media screen and (max-width:810px)and (min-width:656px){.customer-info .customer-name{font-size:16px}.widefat th{max-width:100%!important;display:table-cell}}@media screen and (max-width:656px){.edd-item-info.customer-info{position:relative}.customer-info .customer-name{font-size:16px}#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-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}#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..7258245e365 --- /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;white-space:nowrap}#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..ff76c815250 --- /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;white-space:nowrap}#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/stripe/card-elements.scss b/assets/css/frontend/stripe/card-elements.scss new file mode 100644 index 00000000000..6617ae3922b --- /dev/null +++ b/assets/css/frontend/stripe/card-elements.scss @@ -0,0 +1,396 @@ +/** + * Internal dependencices + */ + @import './frontend/shared.scss'; + @import './frontend/modal.scss'; + @import './frontend/payment-request-button.scss'; + + #edd_checkout_form_wrap .edd-card-selector-radio label { + display: inline; + vertical-align: middle; + font-weight: normal; + line-height: 24px; + font-size: 100%; + margin: 0px; + } + + .edd-card-selector-radio .edd-stripe-card-radio-item { + width: 100%; + font-size: 15px; + padding: 8px 12px; + border: 1px solid transparent; + + label { + line-height: 1; + margin: 0; + display: flex; + align-items: center; + + .add-new-card, + .card-label { + margin-left: 8px; + } + } + } + + .edd-card-selector-radio .edd-stripe-card-radio-item.selected { + border: 1px solid #f0f0f0; + background-color: #fcfcfc; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + } + + .edd-card-selector-radio div.card-label { + width: 65%; + display: inline-block; + } + + .edd-card-selector-radio span.card-is-default { + font-style: italic; + } + + .edd-card-selector-radio span.card-expired { + color: #ff3333; + } + + .edd-stripe-card-selector + .edd-stripe-new-card { + margin-top: 20px; + } + + #edd-stripe-manage-cards .edd-stripe-add-new-card { + margin: 20px 0 10px; + } + + #edd-stripe-manage-cards div.edd-stripe-card-item { + list-style: none; + width: 100%; + display: inline-grid; + border: 1px solid #f0f0f0; + padding: 5px 10px; + min-height: 100px; + margin-bottom: 10px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + margin-left: 0px; + } + + .edd-stripe-card-item > span { + display: block; + } + + .edd-stripe-card-item .card-meta > span { + color: #999; + display: block; + } + + .edd-stripe-card-item .card-actions a { + text-decoration: none; + } + + .edd-stripe-card-item .card-actions a.delete { + color: #ff3333; + } + + .card-actions .edd-loading-ajax { + display: inline-block; + margin-right: 4px; + } + + .edd-stripe-card-item .card-update-form { + display: none; + } + + .edd-stripe-card-item .card-update-form label { + font-weight: bold; + } + + .edd-stripe-card-item .card-update-form input { + margin-bottom: 3px; + } + + .edd-stripe-card-item .card-update-form select { + background: #fff; + border: 1px solid #e4e4e4; + height: 40px; + margin-right: 3px; + } + + .edd-stripe-card-item .card-update-form select:first-of-type { + margin-right: 3px; + } + + .edd-stripe-card-item .card-address-fields input, + .edd-stripe-card-item .card-address-fields select { + width: 49%; + display: inline-block; + } + + /* Checkout Fields */ + .edd-stripe-add-new-card label { + font-weight: bold; + display: block; + position: relative; + line-height: 100%; + font-size: 95%; + margin: 0 0 5px; + } + .edd-stripe-add-new-card label:after { + display: block; + visibility: hidden; + float: none; + clear: both; + height: 0; + text-indent: -9999px; + content: "."; + } + .edd-stripe-add-new-card span.edd-description { + color: #666; + font-size: 80%; + display: block; + margin: 0 0 5px; + } + .edd-stripe-add-new-card input.edd-input, + .edd-stripe-add-new-card textarea.edd-input { + display: inline-block; + width: 70%; + } + .edd-stripe-add-new-card select.edd-select { + display: block; + width: 60%; + } + .edd-stripe-add-new-card select.edd-select.edd-select-small { + display: inline; + width: auto; + } + .edd-stripe-add-new-card input.edd-input.error, + .edd-stripe-add-new-card textarea.edd-input.error { + border-color: #c4554e; + } + .edd-stripe-add-new-card > p { + margin: 0 0 21px; + } + .edd-stripe-add-new-card span.edd-required-indicator { + color: #b94a48; + display: inline; + } + .edd-stripe-add-new-card textarea, + .edd-stripe-add-new-card input[type="text"], + .edd-stripe-add-new-card input[type="email"], + .edd-stripe-add-new-card input[type="password"], + .edd-stripe-add-new-card input[type="tel"] { + padding: 4px 6px; + } + .edd-stripe-add-new-card input[type="radio"] { + border: none; + margin-right: 5px; + } + .edd-stripe-add-new-card input[type="checkbox"] { + display: inline-block; + margin: 0 5px 0 0; + } + .edd-stripe-add-new-card input[type="checkbox"] + label, + .edd-stripe-add-new-card input[type="checkbox"] + label:after { + display: inline; + } + .edd-stripe-add-new-card .edd-payment-icons { + height: 32px; + display: block; + margin: 0 0 8px; + } + .edd-stripe-add-new-card .edd-payment-icons img.payment-icon { + max-height: 32px; + width: auto; + margin: 0 3px 0 0; + float: left; + background: none; + padding: 0; + border: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + } + .edd-stripe-add-new-card #edd-payment-mode-wrap label { + display: inline-block; + margin: 0 20px 0 0; + } + .edd-stripe-add-new-card #edd-payment-mode-wrap .edd-payment-mode-label { + font-weight: bold; + display: inline-block; + position: relative; + margin-bottom: 5px; + } + .edd-stripe-add-new-card fieldset { + border: 1px solid #eee; + padding: 1.387em; + margin: 0 0 21px; + } + .edd-stripe-add-new-card #edd_purchase_submit, + .edd-stripe-add-new-card #edd_discount_code, + .edd-stripe-add-new-card #edd_register_account_fields { + padding: 0; + border: none; + } + .edd-stripe-add-new-card fieldset fieldset { + margin: 0; + border: none; + padding: 0; + } + .edd-stripe-add-new-card #edd-login-account-wrap, + .edd-stripe-add-new-card #edd-new-account-wrap, + .edd-stripe-add-new-card #edd_show_discount, + .edd-stripe-add-new-card .edd-cart-adjustment, + .edd-stripe-add-new-card #edd_final_total_wrap { + background: #fafafa; + color: #666; + padding: 0.5em 1.387em; + } + .edd-stripe-add-new-card #edd-discount-code-wrap, + .edd-stripe-add-new-card #edd_final_total_wrap, + .edd-stripe-add-new-card #edd_show_discount { + border: 1px solid #eee; + } + .edd-stripe-add-new-card .edd-cart-adjustment { + padding: 1.387em; + } + .edd-stripe-add-new-card .edd-cart-adjustment input.edd-input, + .edd-stripe-add-new-card .edd-cart-adjustment input.edd-submit { + display: inline-block; + } + .edd-stripe-add-new-card .edd-cart-adjustment input.edd-submit { + padding: 3px 12px; + margin-bottom: 2px; + } + .edd-stripe-add-new-card #edd-discount-error-wrap { + width: 100%; + display: inline-block; + margin: 1em 0 0; + } + .edd-stripe-add-new-card #edd-new-account-wrap, + .edd-stripe-add-new-card #edd-login-account-wrap { + margin: -1.387em -1.387em 21px; + border-left: none; + border-right: none; + border-top: none; + } + .edd-stripe-add-new-card #edd_payment_mode_select { + margin-bottom: 21px; + } + .edd-stripe-add-new-card fieldset#edd_register_fields #edd_checkout_user_info { + margin-bottom: 21px; + } + .edd-stripe-add-new-card fieldset#edd_register_account_fields legend { + padding-top: 11px; + } + .edd-stripe-add-new-card fieldset#edd_register_account_fields p.edd_register_password, + .edd-stripe-add-new-card fieldset#edd_register_account_fields p.edd_login_password { + margin: 0; + } + .edd-stripe-add-new-card fieldset#edd_cc_fields { + border: 1px solid #f0f0f0; + background: #f9f9f9; + position: relative; + } + .edd-stripe-add-new-card fieldset#edd_cc_fields legend { + border: none; + padding: 0; + } + .edd-stripe-add-new-card fieldset p:last-child { + margin-bottom: 0; + } + #edd_secure_site_wrapper { + padding: 4px 4px 4px 0; + font-weight: bold; + } + .edd-stripe-add-new-card span.exp-divider { + display: inline; + } + + /** + * Stripe Elements - Card + */ + .edd-stripe-card-element.StripeElement, + .edd-stripe-card-exp-element.StripeElement, + .edd-stripe-card-cvc-element.StripeElement { + box-sizing: border-box; + padding: 10px 12px; + border: 1px solid #ccc; + background-color: white; + } + + .edd-stripe-card-element.StripeElement--invalid { + border-color: #c4554e !important; + } + + #edd-card-wrap { + position: relative; + } + + #edd-card-details-wrap { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + } + + #edd-card-details-wrap p:empty { + width: 100%; + } + + #edd-card-exp-wrap, + #edd-card-cvv-wrap { + width: 48%; + } + + #edd-stripe-card-element-wrapper { + position: relative; + } + + #edd_checkout_form_wrap .edd-stripe-new-card span.card-type { + background-size: 32px 24px !important; + width: 32px; + height: 24px; + top: 50%; + transform: translate3d(0, -50%, 0); + right: 10px; + } + + /** + * "Buy Now" modal. + */ + .edds-buy-now-modal { + width: 500px; + + .edds-modal__close { + padding: 0.5rem; + } + + #edd_checkout_form_wrap { + + input.edd-input, + textarea.edd-input { + width: 100%; + } + + #edd_purchase_submit { + margin-top: 1.5rem; + margin-bottom: 0; + } + } + + .edds-field-spacer-shim { + margin-bottom: 1rem; + } + + .edd-alert-error { + margin: 20px 0; + } + + #edd-stripe-card-errors:not(:empty) { + margin-bottom: 20px; + + .edd-alert-error { + margin: 0; + } + } + } \ No newline at end of file diff --git a/assets/css/frontend/stripe/frontend/modal.scss b/assets/css/frontend/stripe/frontend/modal.scss new file mode 100644 index 00000000000..05c5a7bff15 --- /dev/null +++ b/assets/css/frontend/stripe/frontend/modal.scss @@ -0,0 +1,143 @@ +:root { + --edds-modal-grid-unit: 1rem; + --edds-modal-overlay: rgba(0, 0, 0, 0.60); +} + +.edds-modal__overlay { + z-index: 9999; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: var(--edds-modal-overlay); + display: flex; + justify-content: center; + align-items: center; +} + +.edds-modal__container { + background-color: #fff; + min-width: 350px; + max-width: 90vw; + max-height: 90vh; + box-sizing: border-box; + overflow-y: auto; +} + +.admin-bar .edds-modal__container { + margin-top: 32px; +} + +.edds-modal__header { + padding: calc(var(--edds-modal-grid-unit) * 1.5); + display: flex; + justify-content: space-between; + align-items: center; + position: sticky; + top: 0; + z-index: 2; + background: #fff; + border-bottom: 1px solid #eee; +} + +.edds-modal__title { + text-align: left; + font-size: 150%; + margin: 0; +} + +.edds-modal__close { + line-height: 1; + padding: 1rem; + + &:before { + content: "\2715"; + } +} + +.edds-modal__content { + margin: calc(var(--edds-modal-grid-unit) * 1.5); +} + +/** + * Animations + */ +@keyframes eddsSlideIn { + from { + transform: translateY(15%); + } + to { + transform: translateY(0); + } +} + +@keyframes eddsSlideOut { + from { + transform: translateY(0); + } + to { + transform: translateY(15%); + } +} + +.edds-modal.has-slide { + display: none; +} + +.edds-modal.has-slide.is-open { + display: block; +} + +.edds-modal.has-slide[aria-hidden="false"] .edds-modal__container { + animation: eddsSlideIn 0.3s cubic-bezier(0.0, 0.0, 0.2, 1); +} + +.edds-modal.has-slide[aria-hidden="true"] .edds-modal__container { + animation: eddsSlideOut 0.3s cubic-bezier(0.0, 0.0, 0.2, 1); +} + +.edds-modal.has-slide .edds-modal__container, +.edds-modal.has-slide .edds-modal__overlay { + will-change: transform; +} + + /** + * "Buy Now" modal. + */ + .edds-buy-now-modal { + width: 500px; + + .edds-modal__close { + padding: 0.5rem; + } + + #edd_checkout_form_wrap { + + input.edd-input, + textarea.edd-input { + width: 100%; + } + + #edd_purchase_submit { + margin-top: 1.5rem; + margin-bottom: 0; + } + } + + .edds-field-spacer-shim { + margin-bottom: 1rem; + } + + .edd-alert-error { + margin: 20px 0; + } + + #edd-stripe-card-errors:not(:empty) { + margin-bottom: 20px; + + .edd-alert-error { + margin: 0; + } + } +} \ No newline at end of file diff --git a/assets/css/frontend/stripe/frontend/payment-request-button.scss b/assets/css/frontend/stripe/frontend/payment-request-button.scss new file mode 100644 index 00000000000..e4198ee0f8c --- /dev/null +++ b/assets/css/frontend/stripe/frontend/payment-request-button.scss @@ -0,0 +1,83 @@ +.edds-prb { + margin: 15px 0; + display: none; + + &__or { + font-size: 90%; + text-align: center; + margin: 15px 0; + overflow: hidden; + + &::before, + &::after { + background-color: rgba(0, 0, 0, .10); + content: ""; + display: inline-block; + height: 1px; + position: relative; + vertical-align: middle; + width: 50%; + } + + &::before { + right: 0.5em; + margin-left: -50%; + } + + &::after { + left: 0.5em; + margin-right: -50%; + } + } +} + +@mixin loadingState { + + &.loading { + position: relative; + + &::after { + content: ""; + position: absolute; + left: 0; + width: 100%; + height: 100%; + top: 0; + z-index: 100; + } + + > * { + opacity: 0.65; + } + } +} + +/** + * Purchase link loading state. + * + * Disables interaction while redirecting. + */ +.edd_download_purchase_form { + + @include loadingState; +} + +/** + * Checkout + */ +#edd_checkout_form_wrap { + + @include loadingState; + + &:not(.edd-prb--is-active) #edd-payment-mode-wrap #edd-gateway-option-stripe-prb { + display: none !important; + } + + .edds-prb { + margin-bottom: 0; + } + + .edds-prb__or { + display: none; + } +} diff --git a/assets/css/frontend/stripe/frontend/shared.scss b/assets/css/frontend/stripe/frontend/shared.scss new file mode 100644 index 00000000000..e9bc5ef2441 --- /dev/null +++ b/assets/css/frontend/stripe/frontend/shared.scss @@ -0,0 +1,3 @@ +#edd-stripe-card-errors:not(:empty) { + margin: 20px 0 0; +} \ No newline at end of file diff --git a/assets/css/frontend/stripe/payment-elements.scss b/assets/css/frontend/stripe/payment-elements.scss new file mode 100644 index 00000000000..f7faadb31f6 --- /dev/null +++ b/assets/css/frontend/stripe/payment-elements.scss @@ -0,0 +1,29 @@ +/** + * Internal dependencices + */ + @import './frontend/shared.scss'; + @import './frontend/modal.scss'; + +#edd_purchase_submit { + + & #edd-purchase-button { + &[data-edd-button-state="disabled"] + { + opacity: 0.5; + cursor: not-allowed; + } + + &[data-edd-button-state="updating"], + [data-edd-button-state="processing"] { + opacity: 0.5; + cursor: wait; + } + } +} + +#card_name, +.card-name { + padding: 8px !important; + width: 100% !important; + box-sizing: border-box; +} \ 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..61b1e7b59d6 --- /dev/null +++ b/assets/css/frontend/style.scss @@ -0,0 +1,1060 @@ +/** + * 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; + white-space: nowrap; +} + +#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/stripe-admin-rtl.min.css b/assets/css/stripe-admin-rtl.min.css new file mode 100644 index 00000000000..b5a34fdbd2d --- /dev/null +++ b/assets/css/stripe-admin-rtl.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.edds-stripe-connect-acount-info .display-name,.edds-stripe-connect-acount-info .info{display:block}.edds-stripe-connect-acount-info .display-name{font-weight:700}.edds-stripe-connect-acount-info.loading span{display:block}.edds-stripe-connect-acount-info.loading .account-name,.edds-stripe-connect-acount-info.loading .info{animation:skeleton-loading 1s linear infinite alternate;width:200px;height:14px;margin:.25rem 0}#edds-stripe-disconnect-reconnect.loading{animation:skeleton-loading 1s linear infinite alternate;width:350px;height:1rem;margin:.5rem 0}#edds-payment-gateways-stripe-unmet-requirements{margin:-10px -16px 0 0;padding-top:10px}#edds-payment-gateways-stripe-unmet-requirements input[type=checkbox]{margin:0 0 0 6px}.edds-requirements-not-met th{display:none}.edds-requirements-not-met td{padding:0}.edd-stripe-connect{display:inline-block;margin-bottom:1px;background-image:linear-gradient(#28a0e5,#015e94);-webkit-font-smoothing:antialiased;border:0;padding:1px;height:30px;text-decoration:none;border-radius:4px;box-shadow:0 1px 0 rgba(0,0,0,.2);cursor:pointer;-webkit-user-select:none;-ms-user-select:none;user-select:none}.edd-stripe-connect span{display:block;position:relative;padding:0 44px 0 12px;height:30px;background:#1275ff;background-image:linear-gradient(#7dc5ee,#008cdd 85%,#30a2e4);font-size:14px;line-height:30px;color:#fff;font-weight:700;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;text-shadow:0 -1px 0 rgba(0,0,0,.2);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.25);border-radius:3px}.edd-stripe-connect span:before{content:"";display:block;position:absolute;right:11px;top:50%;width:23px;height:24px;margin-top:-12px;background-repeat:no-repeat;background-size:23px 24px}.edd-stripe-connect:active{background:#005d93}.edd-stripe-connect:active span{color:#eee;background:#008cdd;background-image:linear-gradient(#008cdd,#008cdd 85%,#239adf);box-shadow:inset 0 1px 0 rgba(0,0,0,.1)}.edd-stripe-connect.blue span:before,.edd-stripe-connect span:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAYCAYAAAARfGZ1AAAKRGlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUFNcXx9/MbC+0XZYiZem9twWkLr1IlSYKy+4CS1nWZRewN0QFIoqICFYkKGLAaCgSK6JYCAgW7AEJIkoMRhEVlczGHPX3Oyf5/U7eH3c+8333nnfn3vvOGQAoASECYQ6sAEC2UCKO9PdmxsUnMPG9AAZEgAM2AHC4uaLQKL9ogK5AXzYzF3WS8V8LAuD1LYBaAK5bBIQzmX/p/+9DkSsSSwCAwtEAOx4/l4tyIcpZ+RKRTJ9EmZ6SKWMYI2MxmiDKqjJO+8Tmf/p8Yk8Z87KFPNRHlrOIl82TcRfKG/OkfJSREJSL8gT8fJRvoKyfJc0WoPwGZXo2n5MLAIYi0yV8bjrK1ihTxNGRbJTnAkCgpH3FKV+xhF+A5gkAO0e0RCxIS5cwjbkmTBtnZxYzgJ+fxZdILMI53EyOmMdk52SLOMIlAHz6ZlkUUJLVlokW2dHG2dHRwtYSLf/n9Y+bn73+GWS9/eTxMuLPnkGMni/al9gvWk4tAKwptDZbvmgpOwFoWw+A6t0vmv4+AOQLAWjt++p7GLJ5SZdIRC5WVvn5+ZYCPtdSVtDP6386fPb8e/jqPEvZeZ9rx/Thp3KkWRKmrKjcnKwcqZiZK+Jw+UyL/x7ifx34VVpf5WEeyU/li/lC9KgYdMoEwjS03UKeQCLIETIFwr/r8L8M+yoHGX6aaxRodR8BPckSKPTRAfJrD8DQyABJ3IPuQJ/7FkKMAbKbF6s99mnuUUb3/7T/YeAy9BXOFaQxZTI7MprJlYrzZIzeCZnBAhKQB3SgBrSAHjAGFsAWOAFX4Al8QRAIA9EgHiwCXJAOsoEY5IPlYA0oAiVgC9gOqsFeUAcaQBM4BtrASXAOXARXwTVwE9wDQ2AUPAOT4DWYgSAID1EhGqQGaUMGkBlkC7Egd8gXCoEioXgoGUqDhJAUWg6tg0qgcqga2g81QN9DJ6Bz0GWoH7oDDUPj0O/QOxiBKTAd1oQNYSuYBXvBwXA0vBBOgxfDS+FCeDNcBdfCR+BW+Bx8Fb4JD8HP4CkEIGSEgeggFggLYSNhSAKSioiRlUgxUonUIk1IB9KNXEeGkAnkLQaHoWGYGAuMKyYAMx/DxSzGrMSUYqoxhzCtmC7MdcwwZhLzEUvFamDNsC7YQGwcNg2bjy3CVmLrsS3YC9ib2FHsaxwOx8AZ4ZxwAbh4XAZuGa4UtxvXjDuL68eN4KbweLwa3gzvhg/Dc/ASfBF+J/4I/gx+AD+Kf0MgE7QJtgQ/QgJBSFhLqCQcJpwmDBDGCDNEBaIB0YUYRuQRlxDLiHXEDmIfcZQ4Q1IkGZHcSNGkDNIaUhWpiXSBdJ/0kkwm65KdyRFkAXk1uYp8lHyJPEx+S1GimFLYlESKlLKZcpBylnKH8pJKpRpSPakJVAl1M7WBep76kPpGjiZnKRcox5NbJVcj1yo3IPdcnihvIO8lv0h+qXyl/HH5PvkJBaKCoQJbgaOwUqFG4YTCoMKUIk3RRjFMMVuxVPGw4mXFJ0p4JUMlXyWeUqHSAaXzSiM0hKZHY9O4tHW0OtoF2igdRzeiB9Iz6CX07+i99EllJWV75RjlAuUa5VPKQwyEYcgIZGQxyhjHGLcY71Q0VbxU+CqbVJpUBlSmVeeoeqryVYtVm1Vvqr5TY6r5qmWqbVVrU3ugjlE3VY9Qz1ffo35BfWIOfY7rHO6c4jnH5tzVgDVMNSI1lmkc0OjRmNLU0vTXFGnu1DyvOaHF0PLUytCq0DqtNa5N03bXFmhXaJ/RfspUZnoxs5hVzC7mpI6GToCOVGe/Tq/OjK6R7nzdtbrNug/0SHosvVS9Cr1OvUl9bf1Q/eX6jfp3DYgGLIN0gx0G3QbThkaGsYYbDNsMnxipGgUaLTVqNLpvTDX2MF5sXGt8wwRnwjLJNNltcs0UNnUwTTetMe0zg80czQRmu836zbHmzuZC81rzQQuKhZdFnkWjxbAlwzLEcq1lm+VzK32rBKutVt1WH60drLOs66zv2SjZBNmstemw+d3W1JZrW2N7w45q52e3yq7d7oW9mT3ffo/9bQeaQ6jDBodOhw+OTo5ixybHcSd9p2SnXU6DLDornFXKuuSMdfZ2XuV80vmti6OLxOWYy2+uFq6Zroddn8w1msufWzd3xE3XjeO2323Ineme7L7PfchDx4PjUevxyFPPk+dZ7znmZeKV4XXE67m3tbfYu8V7mu3CXsE+64P4+PsU+/T6KvnO9632fein65fm1+g36e/gv8z/bAA2IDhga8BgoGYgN7AhcDLIKWhFUFcwJTgquDr4UYhpiDikIxQODQrdFnp/nsE84by2MBAWGLYt7EG4Ufji8B8jcBHhETURjyNtIpdHdkfRopKiDke9jvaOLou+N994vnR+Z4x8TGJMQ8x0rE9seexQnFXcirir8erxgvj2BHxCTEJ9wtQC3wXbF4wmOiQWJd5aaLSwYOHlReqLshadSpJP4iQdT8YmxyYfTn7PCePUcqZSAlN2pUxy2dwd3Gc8T14Fb5zvxi/nj6W6pZanPklzS9uWNp7ukV6ZPiFgC6oFLzICMvZmTGeGZR7MnM2KzWrOJmQnZ58QKgkzhV05WjkFOf0iM1GRaGixy+LtiyfFweL6XCh3YW67hI7+TPVIjaXrpcN57nk1eW/yY/KPFygWCAt6lpgu2bRkbKnf0m+XYZZxl3Uu11m+ZvnwCq8V+1dCK1NWdq7SW1W4anS1/+pDa0hrMtf8tNZ6bfnaV+ti13UUahauLhxZ77++sUiuSFw0uMF1w96NmI2Cjb2b7Dbt3PSxmFd8pcS6pLLkfSm39Mo3Nt9UfTO7OXVzb5lj2Z4tuC3CLbe2emw9VK5YvrR8ZFvottYKZkVxxavtSdsvV9pX7t1B2iHdMVQVUtW+U3/nlp3vq9Orb9Z41zTv0ti1adf0bt7ugT2ee5r2au4t2ftun2Df7f3++1trDWsrD+AO5B14XBdT1/0t69uGevX6kvoPB4UHhw5FHupqcGpoOKxxuKwRbpQ2jh9JPHLtO5/v2pssmvY3M5pLjoKj0qNPv0/+/tax4GOdx1nHm34w+GFXC62luBVqXdI62ZbeNtQe395/IuhEZ4drR8uPlj8ePKlzsuaU8qmy06TThadnzyw9M3VWdHbiXNq5kc6kznvn487f6Iro6r0QfOHSRb+L57u9us9ccrt08rLL5RNXWFfarjpebe1x6Gn5yeGnll7H3tY+p772a87XOvrn9p8e8Bg4d93n+sUbgTeu3px3s//W/Fu3BxMHh27zbj+5k3Xnxd28uzP3Vt/H3i9+oPCg8qHGw9qfTX5uHnIcOjXsM9zzKOrRvRHuyLNfcn95P1r4mPq4ckx7rOGJ7ZOT437j154ueDr6TPRsZqLoV8Vfdz03fv7Db56/9UzGTY6+EL+Y/b30pdrLg6/sX3VOhU89fJ39ema6+I3am0NvWW+738W+G5vJf49/X/XB5EPHx+CP92ezZ2f/AAOY8/wRDtFgAAADQklEQVRIDbWVaUiUQRjHZ96dXY/d1fYQj1U03dJSw9YkFgy6DIkILRArQSSC7PjQjQQqVH7oQ0GHQUWgpQhKHzoNSqiUwpXcsrwIjzVtPVrzbPV9Z6bZhYV3N3WXYAeGmWeeZ37z8J95GEgpBf5oeXn1Es4fYAdzPDlM6je4RBYhR+LMU89UxiCBGiCgkUwsBYSA+SlPKLQBQAYEAZm+3j42K96z3NyOF7VOeMrp62opRcacjPW5+43rDTpNSKQ8QKZAEg7xmPCTs/O27uGJgXuNbW0pxyvLfTmAEBzthEsFZLxRvPdi5rpYo2cmUiQJDA4IVeo0obGdlvGfXUPj0Sym2zPuHxvzcWjDyVupJ/YYizKTGNjLw/HiduNTAqIRIUJ6Vpp+ky8bCSFgwQ2xgkGxFi1ioNWEBGuJB31gbLIv/2pd7SpFoGxtpCYkLSEq4ptlzIYFO7tc7w0TKkeEYg5ADnrWkkYhD8s26GPq3nW0WKxTptftPYBI4Mj3O2fHvKNZBMVSDmMwarXNjDkSF3d5kExZeiCr8M2VI+VFu9IvsPcYtzAvkfoEZkEEE45jMppq3ppbCNPFIY1nD1cpo07lbMmvOXeoDCF8BLKy9uUAAjDkBh+c6bz78mNtVVP7MwET7JBnqb4xXpdWVpC1OVzWn+ELHLCsneX/s7rkRWl1463cy1U3WroG21jhCGKJXPOtKQnpAuENvsAppgDB3TcDVIrpDHbK5Kd+y7W8iodNybHh22rOHyxUK+UaMYjZaoyp25rYL54TSihSKmwZ14v3lc3ZFxdbeywjn/tGJnkmzrydX1ApxOEACKymmXLYfXVpi1JMEOGxPi1ep18doY4r2J7uFumQQ9yGf01bMcZW8dpyc0oIjxxpuC5wuUDX+ovWrnYeg3aXvdLIqnmOvXPsfH6uA5YbTb1DX8ofvTLzTy6ZV4K6fAw+gXiATfdffmjeaUgc1UdpdWplsCooQBrEnqUw82dhdnjit/Vxc4f59tP3DRjzJvYteqrl4rmNlJIfrOwpgNklesDRNQBCHYtQAQqD2CgACNjHAJnG1EyfV/S67fZiJB5t2OGEe4n7L3fS4fpEv/2hUEATfoPbuam5v8N7nps70YTbAAAAAElFTkSuQmCC")}.edds-stripe-connect-acount-info span.edd-pro-upgrade a{color:#1da867;font-weight:600;text-decoration:none}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-device-pixel-ratio:1.5){.edd-stripe-connect.blue span:before,.edd-stripe-connect span:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAwCAYAAABuZUjcAAAKRGlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUFNcXx9/MbC+0XZYiZem9twWkLr1IlSYKy+4CS1nWZRewN0QFIoqICFYkKGLAaCgSK6JYCAgW7AEJIkoMRhEVlczGHPX3Oyf5/U7eH3c+8333nnfn3vvOGQAoASECYQ6sAEC2UCKO9PdmxsUnMPG9AAZEgAM2AHC4uaLQKL9ogK5AXzYzF3WS8V8LAuD1LYBaAK5bBIQzmX/p/+9DkSsSSwCAwtEAOx4/l4tyIcpZ+RKRTJ9EmZ6SKWMYI2MxmiDKqjJO+8Tmf/p8Yk8Z87KFPNRHlrOIl82TcRfKG/OkfJSREJSL8gT8fJRvoKyfJc0WoPwGZXo2n5MLAIYi0yV8bjrK1ihTxNGRbJTnAkCgpH3FKV+xhF+A5gkAO0e0RCxIS5cwjbkmTBtnZxYzgJ+fxZdILMI53EyOmMdk52SLOMIlAHz6ZlkUUJLVlokW2dHG2dHRwtYSLf/n9Y+bn73+GWS9/eTxMuLPnkGMni/al9gvWk4tAKwptDZbvmgpOwFoWw+A6t0vmv4+AOQLAWjt++p7GLJ5SZdIRC5WVvn5+ZYCPtdSVtDP6386fPb8e/jqPEvZeZ9rx/Thp3KkWRKmrKjcnKwcqZiZK+Jw+UyL/x7ifx34VVpf5WEeyU/li/lC9KgYdMoEwjS03UKeQCLIETIFwr/r8L8M+yoHGX6aaxRodR8BPckSKPTRAfJrD8DQyABJ3IPuQJ/7FkKMAbKbF6s99mnuUUb3/7T/YeAy9BXOFaQxZTI7MprJlYrzZIzeCZnBAhKQB3SgBrSAHjAGFsAWOAFX4Al8QRAIA9EgHiwCXJAOsoEY5IPlYA0oAiVgC9gOqsFeUAcaQBM4BtrASXAOXARXwTVwE9wDQ2AUPAOT4DWYgSAID1EhGqQGaUMGkBlkC7Egd8gXCoEioXgoGUqDhJAUWg6tg0qgcqga2g81QN9DJ6Bz0GWoH7oDDUPj0O/QOxiBKTAd1oQNYSuYBXvBwXA0vBBOgxfDS+FCeDNcBdfCR+BW+Bx8Fb4JD8HP4CkEIGSEgeggFggLYSNhSAKSioiRlUgxUonUIk1IB9KNXEeGkAnkLQaHoWGYGAuMKyYAMx/DxSzGrMSUYqoxhzCtmC7MdcwwZhLzEUvFamDNsC7YQGwcNg2bjy3CVmLrsS3YC9ib2FHsaxwOx8AZ4ZxwAbh4XAZuGa4UtxvXjDuL68eN4KbweLwa3gzvhg/Dc/ASfBF+J/4I/gx+AD+Kf0MgE7QJtgQ/QgJBSFhLqCQcJpwmDBDGCDNEBaIB0YUYRuQRlxDLiHXEDmIfcZQ4Q1IkGZHcSNGkDNIaUhWpiXSBdJ/0kkwm65KdyRFkAXk1uYp8lHyJPEx+S1GimFLYlESKlLKZcpBylnKH8pJKpRpSPakJVAl1M7WBep76kPpGjiZnKRcox5NbJVcj1yo3IPdcnihvIO8lv0h+qXyl/HH5PvkJBaKCoQJbgaOwUqFG4YTCoMKUIk3RRjFMMVuxVPGw4mXFJ0p4JUMlXyWeUqHSAaXzSiM0hKZHY9O4tHW0OtoF2igdRzeiB9Iz6CX07+i99EllJWV75RjlAuUa5VPKQwyEYcgIZGQxyhjHGLcY71Q0VbxU+CqbVJpUBlSmVeeoeqryVYtVm1Vvqr5TY6r5qmWqbVVrU3ugjlE3VY9Qz1ffo35BfWIOfY7rHO6c4jnH5tzVgDVMNSI1lmkc0OjRmNLU0vTXFGnu1DyvOaHF0PLUytCq0DqtNa5N03bXFmhXaJ/RfspUZnoxs5hVzC7mpI6GToCOVGe/Tq/OjK6R7nzdtbrNug/0SHosvVS9Cr1OvUl9bf1Q/eX6jfp3DYgGLIN0gx0G3QbThkaGsYYbDNsMnxipGgUaLTVqNLpvTDX2MF5sXGt8wwRnwjLJNNltcs0UNnUwTTetMe0zg80czQRmu836zbHmzuZC81rzQQuKhZdFnkWjxbAlwzLEcq1lm+VzK32rBKutVt1WH60drLOs66zv2SjZBNmstemw+d3W1JZrW2N7w45q52e3yq7d7oW9mT3ffo/9bQeaQ6jDBodOhw+OTo5ixybHcSd9p2SnXU6DLDornFXKuuSMdfZ2XuV80vmti6OLxOWYy2+uFq6Zroddn8w1msufWzd3xE3XjeO2323Ineme7L7PfchDx4PjUevxyFPPk+dZ7znmZeKV4XXE67m3tbfYu8V7mu3CXsE+64P4+PsU+/T6KvnO9632fein65fm1+g36e/gv8z/bAA2IDhga8BgoGYgN7AhcDLIKWhFUFcwJTgquDr4UYhpiDikIxQODQrdFnp/nsE84by2MBAWGLYt7EG4Ufji8B8jcBHhETURjyNtIpdHdkfRopKiDke9jvaOLou+N994vnR+Z4x8TGJMQ8x0rE9seexQnFXcirir8erxgvj2BHxCTEJ9wtQC3wXbF4wmOiQWJd5aaLSwYOHlReqLshadSpJP4iQdT8YmxyYfTn7PCePUcqZSAlN2pUxy2dwd3Gc8T14Fb5zvxi/nj6W6pZanPklzS9uWNp7ukV6ZPiFgC6oFLzICMvZmTGeGZR7MnM2KzWrOJmQnZ58QKgkzhV05WjkFOf0iM1GRaGixy+LtiyfFweL6XCh3YW67hI7+TPVIjaXrpcN57nk1eW/yY/KPFygWCAt6lpgu2bRkbKnf0m+XYZZxl3Uu11m+ZvnwCq8V+1dCK1NWdq7SW1W4anS1/+pDa0hrMtf8tNZ6bfnaV+ti13UUahauLhxZ77++sUiuSFw0uMF1w96NmI2Cjb2b7Dbt3PSxmFd8pcS6pLLkfSm39Mo3Nt9UfTO7OXVzb5lj2Z4tuC3CLbe2emw9VK5YvrR8ZFvottYKZkVxxavtSdsvV9pX7t1B2iHdMVQVUtW+U3/nlp3vq9Orb9Z41zTv0ti1adf0bt7ugT2ee5r2au4t2ftun2Df7f3++1trDWsrD+AO5B14XBdT1/0t69uGevX6kvoPB4UHhw5FHupqcGpoOKxxuKwRbpQ2jh9JPHLtO5/v2pssmvY3M5pLjoKj0qNPv0/+/tax4GOdx1nHm34w+GFXC62luBVqXdI62ZbeNtQe395/IuhEZ4drR8uPlj8ePKlzsuaU8qmy06TThadnzyw9M3VWdHbiXNq5kc6kznvn487f6Iro6r0QfOHSRb+L57u9us9ccrt08rLL5RNXWFfarjpebe1x6Gn5yeGnll7H3tY+p772a87XOvrn9p8e8Bg4d93n+sUbgTeu3px3s//W/Fu3BxMHh27zbj+5k3Xnxd28uzP3Vt/H3i9+oPCg8qHGw9qfTX5uHnIcOjXsM9zzKOrRvRHuyLNfcn95P1r4mPq4ckx7rOGJ7ZOT437j154ueDr6TPRsZqLoV8Vfdz03fv7Db56/9UzGTY6+EL+Y/b30pdrLg6/sX3VOhU89fJ39ema6+I3am0NvWW+738W+G5vJf49/X/XB5EPHx+CP92ezZ2f/AAOY8/wRDtFgAAAIbklEQVRoBdVZa5BURxU+fZ9z57mzs7PvF4i7srAQSCifMVDERC0jYlzUlJalKeGPlCnL/NEfywpWacoiVZRVJIYfGjGUu5bxj5qHFSAYyQOBEsJzYSHDvnd2dp535j66PX1vNgsULDPs1cr2Vs+9e7v79NfnnnP663MJYwwWYxEWI2iOedEClxabxgkBwjEvOuA9PQOOlSw64JMr4vK8GidYYMcOES4tVSEAAZ8FAUqon1GiAJEEEG0CjFB8cTaxZUMAo1gEqQA0UABprAjPbrUwXnkesgqKP8CBk5vDIenrE+BKmwI+MawA1MbCkdV10cBDflXuVmSxQRbFkCAQZ9U2ZTaONyxKcyXDHjMs83ImV3rz6njmDRPMUZB80zAJOuvvsflkXpTP7DrWyeXcYCqk75AEieawrEoty1vrvlcV0ja3VQdb1rVUQVd9EFqqNIj5ZfDJooPBsCnohq2ldDMynC42XZnW7z09lu25lMxDMl34y0gyvTsBwyewc84Z4MEPpWIzF/MBcLLtNzJISmxZU+PmWETbtqGzfvVja5uguyF02+kCIEJUk6Ex4oMV9XP9ZnQT/nZ24it7XrtoJ5LZ7SjAM+Bg2+0ckAOcbBkQIaZFVzY1bGurjezYfn87PNQZ5+13ZaQRXMzH26Lg8ymfUokQdAR59INOc53GQ6q/Jiiua6oJ7+h9uAPua47cHeLrwHEmQRmTGLHV6x4v+JYwWsOFCGRDn6RKem1rPPrkN9Y0uqAXLN4VwCgjYGEE8rBgMAjwKsF9S9WgLa9qjYcf+Po9jXdlGrfC5Wj8Vg0Lf+ZENAFmpGB9TWTLhmUxUD1UDg/gtudRnK+a4RtkgqQyO+RT5LVrmiLgJcN19gcGNojUWriS5yRQm7pcBTc/vyCKdW1RrWwzOTiYhGf+dRUmcgZosgDVfgWaMCS2V2tO+OzG0MiVjdUwiFiYm9a7O4kJAoZEooV9H4T0O0ofODkKr5+6+nY6V3heVZQpv6ZWaz55qSJJnXjtUBW5pT7k8xeK5u+B0PQdBVbQgTLq9HbQYthyNVSmTT6A/nB0aGpF0K99+trY1F7TNI9PZGXkKUVRtYjGZCIOV1dHR4Ynz8FSLV8BrjK6uiAlpLcmco1ipmgpAaU8rfesboCuumBg31uJbx6+qH0uX9D/em0i85xFhaslKZKA8/82RtYDhd/1MkCuBnjxrLgKB0EQSb5oWO+9O1bZrsy3+Kc3dcH+b99b07NuyXe6P9r8z/am+C9lkuqCjo4qGGkQES76qJcuz/2GOlUoFuVsQS+98frlaSeq8Gkqqctrg7Dz853wwrfugUfXtj3W3tJ8oCletRUEXy1SCSSYHhdu41gFqILcZCrzwkvnJmE0U3JtHefiL7eS2l7th11f7IQ9j65aVh+r+nlzbd2TELJrHPLmIXZX3wyBX8MTQMm8PJ0u9Pe9chGQYy9omvXouHu/thJqI+Ef1sZDm0AMBmfPiQsSPDuY2zhWwSH5ISU5Pjm98x9nRo7+7JVBB3wl5nJz35Vo/z/esBQUVf2+QlkD9Aw42/Ts3Au7ushdAhQ5UzJoOjE+OrV9/1tDR7cNnIax7N2bDX9nm1bUQXdz9Rp/MLwRoqAtDOzcaO7rvDrAWW8vhcatWVNjF6cmJre9embkz1947h3YfXgIUgVzblQldxgFH0ZOr/qULwM15k4Zlci4Vd9ZU5ltY71oObHBnBFQBidmUk8kEsOP7Hntwqsb974NfS8PAh7LKoo23Hw+2R4FQcSzKlDPgFOEyf8kx3HW94kQ7xJgRRdAJG7CyIWxgiXNUN0+k5nJLN83k3n8D8eHN3+1ux5+8uBHIKiWt1G1Rn3IJkiUCcQzU3G0h9qWHMeJdoSrwtr9dl6I6DNjFwRRyxiKnStSqkPJPsGSmZ+mp1P9z2dzOy3Klj31yMdmX9S8V75APEsomMZwT9fz9i6vkW9AvEgQyqrBQM2Dq9rrD0gCgXfHA0jpjIRm2Zcw+3CR2tZl27SnMZFSZ1lWcRwZITeDckresAEXaoKwwBh7/WQubgTOQj5BVjdv7KiBJz7bztMNcHIk03JiONNyfiK/ntv2VMHAMx6BjpoA/Gj9Emdjul7W7e6TeQNDK9WJLRm361P5c1drEmAaymaYoXpfjZoiOk7FHWuh5dxEHmzLHiXM9oyTz9FawRZw65f5yyzXBMpd0JGhFKB5nSwRMVvumDv2cxm4m1f5X4AuWhRePDUOtqEPQJVVGfWcBz1ahmPlTlxzqaJLquYZU1HTvjcTMD6dOULM0n+g5nKposHzdWbo7FgEkDBviWlYx++53XtQ33kvDU8dHAJm6L8usdwEZn09S3qiPed5lcCSLUpI0eEA8620zLbDl6bh8T+egkI+/7Rl6kegcTSPst1QUKaM+brhrjnF2yUQJNxnrGMnR7KbTw5nYFVjyAl98w2+VdvVlA67Dw3BgROjAKa+yyrpz0BKTbJnez1NT6AKrrnA1bEi1av2v3xaiL90dnxL2Kc0rsXc4WpcQEc8AEtiGrRiejmK6WWeMDIxtVwwKExijB5KFuBYIg1cy8dx0dTQ/yQVc78yBXMIqJ5i/VvvkqHdSjXuM/THKy7w2LQJ6fpJms38QiHGvlzBt+RwJv2JQ2elbjyRtjIi1AIRMAsKPuQduHVzr2YW+kIBE5BTwOzzxLKOiMX8QVuWh00IpqD+S0WHtLlzefpLBOZo/IYvEqQPnTX5dxmy4xookqaCjRuT4mMi8g3bxs2KCkj3GFj4+QSzA0RkeskU8iCJeUiBDv09Jt8OPEV6k7DlP3gxxh/dAPymPh/Kf5d897dIOd9P7H8oEd4G1JV8wPGbRadx52sgLmrRAZ99EZ5+LZgV+v+4Llrg/wX6HRCxgvzAAwAAAABJRU5ErkJggg==")}}.edd-text-loading{animation:skeleton-loading 1s infinite alternate}.edd-button__toggle{position:relative;padding:0;width:36px;height:20px;min-height:unset;transition:background .2s ease;border-radius:30px;box-shadow:none;border:none;display:flex;justify-content:center;align-items:center;background:#c3c4c7;margin:0 0 0 12px}.edd-button__toggle--enabled{background:var(--wp-admin-theme-color)}.edd-button__toggle--enabled:after{transform:translateX(-16px)}.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%;background:#fff}.edd-stripe-payment-methods__description .button-group{display:block!important;margin:20px auto;text-align:center}.edd_settingsstripe_payment_methods{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1em}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap{background-color:#fff;border:1px solid #c3c4c7;border-radius:4px;padding:20px;margin:0;justify-content:space-between}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .edd-toggle{gap:8px;margin-bottom:0}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .edd-form-group__control__label{flex-grow:1;display:flex;align-items:center;gap:8px;justify-content:space-between}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .edd-form-group__control__label label{flex-grow:1}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .description{flex-basis:100%}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .edd-icon__placeholder,.edd_settingsstripe_payment_methods .edd-form-group__control--wrap>svg{width:32px;height:32px}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap input[type=checkbox]{order:100}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap input[type=checkbox]:disabled:not(:checked){background-color:rgba(214,54,56,.5)} \ No newline at end of file diff --git a/assets/css/stripe-admin.min.css b/assets/css/stripe-admin.min.css new file mode 100644 index 00000000000..149eed38a4d --- /dev/null +++ b/assets/css/stripe-admin.min.css @@ -0,0 +1 @@ +@keyframes skeleton-loading{0%{background-color:#d1d9e0}to{background-color:#e0e6eb}}.edds-stripe-connect-acount-info .display-name,.edds-stripe-connect-acount-info .info{display:block}.edds-stripe-connect-acount-info .display-name{font-weight:700}.edds-stripe-connect-acount-info.loading span{display:block}.edds-stripe-connect-acount-info.loading .account-name,.edds-stripe-connect-acount-info.loading .info{animation:skeleton-loading 1s linear infinite alternate;width:200px;height:14px;margin:.25rem 0}#edds-stripe-disconnect-reconnect.loading{animation:skeleton-loading 1s linear infinite alternate;width:350px;height:1rem;margin:.5rem 0}#edds-payment-gateways-stripe-unmet-requirements{margin:-10px 0 0 -16px;padding-top:10px}#edds-payment-gateways-stripe-unmet-requirements input[type=checkbox]{margin:0 6px 0 0}.edds-requirements-not-met th{display:none}.edds-requirements-not-met td{padding:0}.edd-stripe-connect{display:inline-block;margin-bottom:1px;background-image:linear-gradient(#28a0e5,#015e94);-webkit-font-smoothing:antialiased;border:0;padding:1px;height:30px;text-decoration:none;border-radius:4px;box-shadow:0 1px 0 rgba(0,0,0,.2);cursor:pointer;-webkit-user-select:none;-ms-user-select:none;user-select:none}.edd-stripe-connect span{display:block;position:relative;padding:0 12px 0 44px;height:30px;background:#1275ff;background-image:linear-gradient(#7dc5ee,#008cdd 85%,#30a2e4);font-size:14px;line-height:30px;color:#fff;font-weight:700;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;text-shadow:0 -1px 0 rgba(0,0,0,.2);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.25);border-radius:3px}.edd-stripe-connect span:before{content:"";display:block;position:absolute;left:11px;top:50%;width:23px;height:24px;margin-top:-12px;background-repeat:no-repeat;background-size:23px 24px}.edd-stripe-connect:active{background:#005d93}.edd-stripe-connect:active span{color:#eee;background:#008cdd;background-image:linear-gradient(#008cdd,#008cdd 85%,#239adf);box-shadow:inset 0 1px 0 rgba(0,0,0,.1)}.edd-stripe-connect.blue span:before,.edd-stripe-connect span:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAYCAYAAAARfGZ1AAAKRGlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUFNcXx9/MbC+0XZYiZem9twWkLr1IlSYKy+4CS1nWZRewN0QFIoqICFYkKGLAaCgSK6JYCAgW7AEJIkoMRhEVlczGHPX3Oyf5/U7eH3c+8333nnfn3vvOGQAoASECYQ6sAEC2UCKO9PdmxsUnMPG9AAZEgAM2AHC4uaLQKL9ogK5AXzYzF3WS8V8LAuD1LYBaAK5bBIQzmX/p/+9DkSsSSwCAwtEAOx4/l4tyIcpZ+RKRTJ9EmZ6SKWMYI2MxmiDKqjJO+8Tmf/p8Yk8Z87KFPNRHlrOIl82TcRfKG/OkfJSREJSL8gT8fJRvoKyfJc0WoPwGZXo2n5MLAIYi0yV8bjrK1ihTxNGRbJTnAkCgpH3FKV+xhF+A5gkAO0e0RCxIS5cwjbkmTBtnZxYzgJ+fxZdILMI53EyOmMdk52SLOMIlAHz6ZlkUUJLVlokW2dHG2dHRwtYSLf/n9Y+bn73+GWS9/eTxMuLPnkGMni/al9gvWk4tAKwptDZbvmgpOwFoWw+A6t0vmv4+AOQLAWjt++p7GLJ5SZdIRC5WVvn5+ZYCPtdSVtDP6386fPb8e/jqPEvZeZ9rx/Thp3KkWRKmrKjcnKwcqZiZK+Jw+UyL/x7ifx34VVpf5WEeyU/li/lC9KgYdMoEwjS03UKeQCLIETIFwr/r8L8M+yoHGX6aaxRodR8BPckSKPTRAfJrD8DQyABJ3IPuQJ/7FkKMAbKbF6s99mnuUUb3/7T/YeAy9BXOFaQxZTI7MprJlYrzZIzeCZnBAhKQB3SgBrSAHjAGFsAWOAFX4Al8QRAIA9EgHiwCXJAOsoEY5IPlYA0oAiVgC9gOqsFeUAcaQBM4BtrASXAOXARXwTVwE9wDQ2AUPAOT4DWYgSAID1EhGqQGaUMGkBlkC7Egd8gXCoEioXgoGUqDhJAUWg6tg0qgcqga2g81QN9DJ6Bz0GWoH7oDDUPj0O/QOxiBKTAd1oQNYSuYBXvBwXA0vBBOgxfDS+FCeDNcBdfCR+BW+Bx8Fb4JD8HP4CkEIGSEgeggFggLYSNhSAKSioiRlUgxUonUIk1IB9KNXEeGkAnkLQaHoWGYGAuMKyYAMx/DxSzGrMSUYqoxhzCtmC7MdcwwZhLzEUvFamDNsC7YQGwcNg2bjy3CVmLrsS3YC9ib2FHsaxwOx8AZ4ZxwAbh4XAZuGa4UtxvXjDuL68eN4KbweLwa3gzvhg/Dc/ASfBF+J/4I/gx+AD+Kf0MgE7QJtgQ/QgJBSFhLqCQcJpwmDBDGCDNEBaIB0YUYRuQRlxDLiHXEDmIfcZQ4Q1IkGZHcSNGkDNIaUhWpiXSBdJ/0kkwm65KdyRFkAXk1uYp8lHyJPEx+S1GimFLYlESKlLKZcpBylnKH8pJKpRpSPakJVAl1M7WBep76kPpGjiZnKRcox5NbJVcj1yo3IPdcnihvIO8lv0h+qXyl/HH5PvkJBaKCoQJbgaOwUqFG4YTCoMKUIk3RRjFMMVuxVPGw4mXFJ0p4JUMlXyWeUqHSAaXzSiM0hKZHY9O4tHW0OtoF2igdRzeiB9Iz6CX07+i99EllJWV75RjlAuUa5VPKQwyEYcgIZGQxyhjHGLcY71Q0VbxU+CqbVJpUBlSmVeeoeqryVYtVm1Vvqr5TY6r5qmWqbVVrU3ugjlE3VY9Qz1ffo35BfWIOfY7rHO6c4jnH5tzVgDVMNSI1lmkc0OjRmNLU0vTXFGnu1DyvOaHF0PLUytCq0DqtNa5N03bXFmhXaJ/RfspUZnoxs5hVzC7mpI6GToCOVGe/Tq/OjK6R7nzdtbrNug/0SHosvVS9Cr1OvUl9bf1Q/eX6jfp3DYgGLIN0gx0G3QbThkaGsYYbDNsMnxipGgUaLTVqNLpvTDX2MF5sXGt8wwRnwjLJNNltcs0UNnUwTTetMe0zg80czQRmu836zbHmzuZC81rzQQuKhZdFnkWjxbAlwzLEcq1lm+VzK32rBKutVt1WH60drLOs66zv2SjZBNmstemw+d3W1JZrW2N7w45q52e3yq7d7oW9mT3ffo/9bQeaQ6jDBodOhw+OTo5ixybHcSd9p2SnXU6DLDornFXKuuSMdfZ2XuV80vmti6OLxOWYy2+uFq6Zroddn8w1msufWzd3xE3XjeO2323Ineme7L7PfchDx4PjUevxyFPPk+dZ7znmZeKV4XXE67m3tbfYu8V7mu3CXsE+64P4+PsU+/T6KvnO9632fein65fm1+g36e/gv8z/bAA2IDhga8BgoGYgN7AhcDLIKWhFUFcwJTgquDr4UYhpiDikIxQODQrdFnp/nsE84by2MBAWGLYt7EG4Ufji8B8jcBHhETURjyNtIpdHdkfRopKiDke9jvaOLou+N994vnR+Z4x8TGJMQ8x0rE9seexQnFXcirir8erxgvj2BHxCTEJ9wtQC3wXbF4wmOiQWJd5aaLSwYOHlReqLshadSpJP4iQdT8YmxyYfTn7PCePUcqZSAlN2pUxy2dwd3Gc8T14Fb5zvxi/nj6W6pZanPklzS9uWNp7ukV6ZPiFgC6oFLzICMvZmTGeGZR7MnM2KzWrOJmQnZ58QKgkzhV05WjkFOf0iM1GRaGixy+LtiyfFweL6XCh3YW67hI7+TPVIjaXrpcN57nk1eW/yY/KPFygWCAt6lpgu2bRkbKnf0m+XYZZxl3Uu11m+ZvnwCq8V+1dCK1NWdq7SW1W4anS1/+pDa0hrMtf8tNZ6bfnaV+ti13UUahauLhxZ77++sUiuSFw0uMF1w96NmI2Cjb2b7Dbt3PSxmFd8pcS6pLLkfSm39Mo3Nt9UfTO7OXVzb5lj2Z4tuC3CLbe2emw9VK5YvrR8ZFvottYKZkVxxavtSdsvV9pX7t1B2iHdMVQVUtW+U3/nlp3vq9Orb9Z41zTv0ti1adf0bt7ugT2ee5r2au4t2ftun2Df7f3++1trDWsrD+AO5B14XBdT1/0t69uGevX6kvoPB4UHhw5FHupqcGpoOKxxuKwRbpQ2jh9JPHLtO5/v2pssmvY3M5pLjoKj0qNPv0/+/tax4GOdx1nHm34w+GFXC62luBVqXdI62ZbeNtQe395/IuhEZ4drR8uPlj8ePKlzsuaU8qmy06TThadnzyw9M3VWdHbiXNq5kc6kznvn487f6Iro6r0QfOHSRb+L57u9us9ccrt08rLL5RNXWFfarjpebe1x6Gn5yeGnll7H3tY+p772a87XOvrn9p8e8Bg4d93n+sUbgTeu3px3s//W/Fu3BxMHh27zbj+5k3Xnxd28uzP3Vt/H3i9+oPCg8qHGw9qfTX5uHnIcOjXsM9zzKOrRvRHuyLNfcn95P1r4mPq4ckx7rOGJ7ZOT437j154ueDr6TPRsZqLoV8Vfdz03fv7Db56/9UzGTY6+EL+Y/b30pdrLg6/sX3VOhU89fJ39ema6+I3am0NvWW+738W+G5vJf49/X/XB5EPHx+CP92ezZ2f/AAOY8/wRDtFgAAADQklEQVRIDbWVaUiUQRjHZ96dXY/d1fYQj1U03dJSw9YkFgy6DIkILRArQSSC7PjQjQQqVH7oQ0GHQUWgpQhKHzoNSqiUwpXcsrwIjzVtPVrzbPV9Z6bZhYV3N3WXYAeGmWeeZ37z8J95GEgpBf5oeXn1Es4fYAdzPDlM6je4RBYhR+LMU89UxiCBGiCgkUwsBYSA+SlPKLQBQAYEAZm+3j42K96z3NyOF7VOeMrp62opRcacjPW5+43rDTpNSKQ8QKZAEg7xmPCTs/O27uGJgXuNbW0pxyvLfTmAEBzthEsFZLxRvPdi5rpYo2cmUiQJDA4IVeo0obGdlvGfXUPj0Sym2zPuHxvzcWjDyVupJ/YYizKTGNjLw/HiduNTAqIRIUJ6Vpp+ky8bCSFgwQ2xgkGxFi1ioNWEBGuJB31gbLIv/2pd7SpFoGxtpCYkLSEq4ptlzIYFO7tc7w0TKkeEYg5ADnrWkkYhD8s26GPq3nW0WKxTptftPYBI4Mj3O2fHvKNZBMVSDmMwarXNjDkSF3d5kExZeiCr8M2VI+VFu9IvsPcYtzAvkfoEZkEEE45jMppq3ppbCNPFIY1nD1cpo07lbMmvOXeoDCF8BLKy9uUAAjDkBh+c6bz78mNtVVP7MwET7JBnqb4xXpdWVpC1OVzWn+ELHLCsneX/s7rkRWl1463cy1U3WroG21jhCGKJXPOtKQnpAuENvsAppgDB3TcDVIrpDHbK5Kd+y7W8iodNybHh22rOHyxUK+UaMYjZaoyp25rYL54TSihSKmwZ14v3lc3ZFxdbeywjn/tGJnkmzrydX1ApxOEACKymmXLYfXVpi1JMEOGxPi1ep18doY4r2J7uFumQQ9yGf01bMcZW8dpyc0oIjxxpuC5wuUDX+ovWrnYeg3aXvdLIqnmOvXPsfH6uA5YbTb1DX8ofvTLzTy6ZV4K6fAw+gXiATfdffmjeaUgc1UdpdWplsCooQBrEnqUw82dhdnjit/Vxc4f59tP3DRjzJvYteqrl4rmNlJIfrOwpgNklesDRNQBCHYtQAQqD2CgACNjHAJnG1EyfV/S67fZiJB5t2OGEe4n7L3fS4fpEv/2hUEATfoPbuam5v8N7nps70YTbAAAAAElFTkSuQmCC")}.edds-stripe-connect-acount-info span.edd-pro-upgrade a{color:#1da867;font-weight:600;text-decoration:none}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-device-pixel-ratio:1.5){.edd-stripe-connect.blue span:before,.edd-stripe-connect span:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAwCAYAAABuZUjcAAAKRGlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUFNcXx9/MbC+0XZYiZem9twWkLr1IlSYKy+4CS1nWZRewN0QFIoqICFYkKGLAaCgSK6JYCAgW7AEJIkoMRhEVlczGHPX3Oyf5/U7eH3c+8333nnfn3vvOGQAoASECYQ6sAEC2UCKO9PdmxsUnMPG9AAZEgAM2AHC4uaLQKL9ogK5AXzYzF3WS8V8LAuD1LYBaAK5bBIQzmX/p/+9DkSsSSwCAwtEAOx4/l4tyIcpZ+RKRTJ9EmZ6SKWMYI2MxmiDKqjJO+8Tmf/p8Yk8Z87KFPNRHlrOIl82TcRfKG/OkfJSREJSL8gT8fJRvoKyfJc0WoPwGZXo2n5MLAIYi0yV8bjrK1ihTxNGRbJTnAkCgpH3FKV+xhF+A5gkAO0e0RCxIS5cwjbkmTBtnZxYzgJ+fxZdILMI53EyOmMdk52SLOMIlAHz6ZlkUUJLVlokW2dHG2dHRwtYSLf/n9Y+bn73+GWS9/eTxMuLPnkGMni/al9gvWk4tAKwptDZbvmgpOwFoWw+A6t0vmv4+AOQLAWjt++p7GLJ5SZdIRC5WVvn5+ZYCPtdSVtDP6386fPb8e/jqPEvZeZ9rx/Thp3KkWRKmrKjcnKwcqZiZK+Jw+UyL/x7ifx34VVpf5WEeyU/li/lC9KgYdMoEwjS03UKeQCLIETIFwr/r8L8M+yoHGX6aaxRodR8BPckSKPTRAfJrD8DQyABJ3IPuQJ/7FkKMAbKbF6s99mnuUUb3/7T/YeAy9BXOFaQxZTI7MprJlYrzZIzeCZnBAhKQB3SgBrSAHjAGFsAWOAFX4Al8QRAIA9EgHiwCXJAOsoEY5IPlYA0oAiVgC9gOqsFeUAcaQBM4BtrASXAOXARXwTVwE9wDQ2AUPAOT4DWYgSAID1EhGqQGaUMGkBlkC7Egd8gXCoEioXgoGUqDhJAUWg6tg0qgcqga2g81QN9DJ6Bz0GWoH7oDDUPj0O/QOxiBKTAd1oQNYSuYBXvBwXA0vBBOgxfDS+FCeDNcBdfCR+BW+Bx8Fb4JD8HP4CkEIGSEgeggFggLYSNhSAKSioiRlUgxUonUIk1IB9KNXEeGkAnkLQaHoWGYGAuMKyYAMx/DxSzGrMSUYqoxhzCtmC7MdcwwZhLzEUvFamDNsC7YQGwcNg2bjy3CVmLrsS3YC9ib2FHsaxwOx8AZ4ZxwAbh4XAZuGa4UtxvXjDuL68eN4KbweLwa3gzvhg/Dc/ASfBF+J/4I/gx+AD+Kf0MgE7QJtgQ/QgJBSFhLqCQcJpwmDBDGCDNEBaIB0YUYRuQRlxDLiHXEDmIfcZQ4Q1IkGZHcSNGkDNIaUhWpiXSBdJ/0kkwm65KdyRFkAXk1uYp8lHyJPEx+S1GimFLYlESKlLKZcpBylnKH8pJKpRpSPakJVAl1M7WBep76kPpGjiZnKRcox5NbJVcj1yo3IPdcnihvIO8lv0h+qXyl/HH5PvkJBaKCoQJbgaOwUqFG4YTCoMKUIk3RRjFMMVuxVPGw4mXFJ0p4JUMlXyWeUqHSAaXzSiM0hKZHY9O4tHW0OtoF2igdRzeiB9Iz6CX07+i99EllJWV75RjlAuUa5VPKQwyEYcgIZGQxyhjHGLcY71Q0VbxU+CqbVJpUBlSmVeeoeqryVYtVm1Vvqr5TY6r5qmWqbVVrU3ugjlE3VY9Qz1ffo35BfWIOfY7rHO6c4jnH5tzVgDVMNSI1lmkc0OjRmNLU0vTXFGnu1DyvOaHF0PLUytCq0DqtNa5N03bXFmhXaJ/RfspUZnoxs5hVzC7mpI6GToCOVGe/Tq/OjK6R7nzdtbrNug/0SHosvVS9Cr1OvUl9bf1Q/eX6jfp3DYgGLIN0gx0G3QbThkaGsYYbDNsMnxipGgUaLTVqNLpvTDX2MF5sXGt8wwRnwjLJNNltcs0UNnUwTTetMe0zg80czQRmu836zbHmzuZC81rzQQuKhZdFnkWjxbAlwzLEcq1lm+VzK32rBKutVt1WH60drLOs66zv2SjZBNmstemw+d3W1JZrW2N7w45q52e3yq7d7oW9mT3ffo/9bQeaQ6jDBodOhw+OTo5ixybHcSd9p2SnXU6DLDornFXKuuSMdfZ2XuV80vmti6OLxOWYy2+uFq6Zroddn8w1msufWzd3xE3XjeO2323Ineme7L7PfchDx4PjUevxyFPPk+dZ7znmZeKV4XXE67m3tbfYu8V7mu3CXsE+64P4+PsU+/T6KvnO9632fein65fm1+g36e/gv8z/bAA2IDhga8BgoGYgN7AhcDLIKWhFUFcwJTgquDr4UYhpiDikIxQODQrdFnp/nsE84by2MBAWGLYt7EG4Ufji8B8jcBHhETURjyNtIpdHdkfRopKiDke9jvaOLou+N994vnR+Z4x8TGJMQ8x0rE9seexQnFXcirir8erxgvj2BHxCTEJ9wtQC3wXbF4wmOiQWJd5aaLSwYOHlReqLshadSpJP4iQdT8YmxyYfTn7PCePUcqZSAlN2pUxy2dwd3Gc8T14Fb5zvxi/nj6W6pZanPklzS9uWNp7ukV6ZPiFgC6oFLzICMvZmTGeGZR7MnM2KzWrOJmQnZ58QKgkzhV05WjkFOf0iM1GRaGixy+LtiyfFweL6XCh3YW67hI7+TPVIjaXrpcN57nk1eW/yY/KPFygWCAt6lpgu2bRkbKnf0m+XYZZxl3Uu11m+ZvnwCq8V+1dCK1NWdq7SW1W4anS1/+pDa0hrMtf8tNZ6bfnaV+ti13UUahauLhxZ77++sUiuSFw0uMF1w96NmI2Cjb2b7Dbt3PSxmFd8pcS6pLLkfSm39Mo3Nt9UfTO7OXVzb5lj2Z4tuC3CLbe2emw9VK5YvrR8ZFvottYKZkVxxavtSdsvV9pX7t1B2iHdMVQVUtW+U3/nlp3vq9Orb9Z41zTv0ti1adf0bt7ugT2ee5r2au4t2ftun2Df7f3++1trDWsrD+AO5B14XBdT1/0t69uGevX6kvoPB4UHhw5FHupqcGpoOKxxuKwRbpQ2jh9JPHLtO5/v2pssmvY3M5pLjoKj0qNPv0/+/tax4GOdx1nHm34w+GFXC62luBVqXdI62ZbeNtQe395/IuhEZ4drR8uPlj8ePKlzsuaU8qmy06TThadnzyw9M3VWdHbiXNq5kc6kznvn487f6Iro6r0QfOHSRb+L57u9us9ccrt08rLL5RNXWFfarjpebe1x6Gn5yeGnll7H3tY+p772a87XOvrn9p8e8Bg4d93n+sUbgTeu3px3s//W/Fu3BxMHh27zbj+5k3Xnxd28uzP3Vt/H3i9+oPCg8qHGw9qfTX5uHnIcOjXsM9zzKOrRvRHuyLNfcn95P1r4mPq4ckx7rOGJ7ZOT437j154ueDr6TPRsZqLoV8Vfdz03fv7Db56/9UzGTY6+EL+Y/b30pdrLg6/sX3VOhU89fJ39ema6+I3am0NvWW+738W+G5vJf49/X/XB5EPHx+CP92ezZ2f/AAOY8/wRDtFgAAAIbklEQVRoBdVZa5BURxU+fZ9z57mzs7PvF4i7srAQSCifMVDERC0jYlzUlJalKeGPlCnL/NEfywpWacoiVZRVJIYfGjGUu5bxj5qHFSAYyQOBEsJzYSHDvnd2dp535j66PX1vNgsULDPs1cr2Vs+9e7v79NfnnnP663MJYwwWYxEWI2iOedEClxabxgkBwjEvOuA9PQOOlSw64JMr4vK8GidYYMcOES4tVSEAAZ8FAUqon1GiAJEEEG0CjFB8cTaxZUMAo1gEqQA0UABprAjPbrUwXnkesgqKP8CBk5vDIenrE+BKmwI+MawA1MbCkdV10cBDflXuVmSxQRbFkCAQZ9U2ZTaONyxKcyXDHjMs83ImV3rz6njmDRPMUZB80zAJOuvvsflkXpTP7DrWyeXcYCqk75AEieawrEoty1vrvlcV0ja3VQdb1rVUQVd9EFqqNIj5ZfDJooPBsCnohq2ldDMynC42XZnW7z09lu25lMxDMl34y0gyvTsBwyewc84Z4MEPpWIzF/MBcLLtNzJISmxZU+PmWETbtqGzfvVja5uguyF02+kCIEJUk6Ex4oMV9XP9ZnQT/nZ24it7XrtoJ5LZ7SjAM+Bg2+0ckAOcbBkQIaZFVzY1bGurjezYfn87PNQZ5+13ZaQRXMzH26Lg8ymfUokQdAR59INOc53GQ6q/Jiiua6oJ7+h9uAPua47cHeLrwHEmQRmTGLHV6x4v+JYwWsOFCGRDn6RKem1rPPrkN9Y0uqAXLN4VwCgjYGEE8rBgMAjwKsF9S9WgLa9qjYcf+Po9jXdlGrfC5Wj8Vg0Lf+ZENAFmpGB9TWTLhmUxUD1UDg/gtudRnK+a4RtkgqQyO+RT5LVrmiLgJcN19gcGNojUWriS5yRQm7pcBTc/vyCKdW1RrWwzOTiYhGf+dRUmcgZosgDVfgWaMCS2V2tO+OzG0MiVjdUwiFiYm9a7O4kJAoZEooV9H4T0O0ofODkKr5+6+nY6V3heVZQpv6ZWaz55qSJJnXjtUBW5pT7k8xeK5u+B0PQdBVbQgTLq9HbQYthyNVSmTT6A/nB0aGpF0K99+trY1F7TNI9PZGXkKUVRtYjGZCIOV1dHR4Ynz8FSLV8BrjK6uiAlpLcmco1ipmgpAaU8rfesboCuumBg31uJbx6+qH0uX9D/em0i85xFhaslKZKA8/82RtYDhd/1MkCuBnjxrLgKB0EQSb5oWO+9O1bZrsy3+Kc3dcH+b99b07NuyXe6P9r8z/am+C9lkuqCjo4qGGkQES76qJcuz/2GOlUoFuVsQS+98frlaSeq8Gkqqctrg7Dz853wwrfugUfXtj3W3tJ8oCletRUEXy1SCSSYHhdu41gFqILcZCrzwkvnJmE0U3JtHefiL7eS2l7th11f7IQ9j65aVh+r+nlzbd2TELJrHPLmIXZX3wyBX8MTQMm8PJ0u9Pe9chGQYy9omvXouHu/thJqI+Ef1sZDm0AMBmfPiQsSPDuY2zhWwSH5ISU5Pjm98x9nRo7+7JVBB3wl5nJz35Vo/z/esBQUVf2+QlkD9Aw42/Ts3Au7ushdAhQ5UzJoOjE+OrV9/1tDR7cNnIax7N2bDX9nm1bUQXdz9Rp/MLwRoqAtDOzcaO7rvDrAWW8vhcatWVNjF6cmJre9embkz1947h3YfXgIUgVzblQldxgFH0ZOr/qULwM15k4Zlci4Vd9ZU5ltY71oObHBnBFQBidmUk8kEsOP7Hntwqsb974NfS8PAh7LKoo23Hw+2R4FQcSzKlDPgFOEyf8kx3HW94kQ7xJgRRdAJG7CyIWxgiXNUN0+k5nJLN83k3n8D8eHN3+1ux5+8uBHIKiWt1G1Rn3IJkiUCcQzU3G0h9qWHMeJdoSrwtr9dl6I6DNjFwRRyxiKnStSqkPJPsGSmZ+mp1P9z2dzOy3Klj31yMdmX9S8V75APEsomMZwT9fz9i6vkW9AvEgQyqrBQM2Dq9rrD0gCgXfHA0jpjIRm2Zcw+3CR2tZl27SnMZFSZ1lWcRwZITeDckresAEXaoKwwBh7/WQubgTOQj5BVjdv7KiBJz7bztMNcHIk03JiONNyfiK/ntv2VMHAMx6BjpoA/Gj9Emdjul7W7e6TeQNDK9WJLRm361P5c1drEmAaymaYoXpfjZoiOk7FHWuh5dxEHmzLHiXM9oyTz9FawRZw65f5yyzXBMpd0JGhFKB5nSwRMVvumDv2cxm4m1f5X4AuWhRePDUOtqEPQJVVGfWcBz1ahmPlTlxzqaJLquYZU1HTvjcTMD6dOULM0n+g5nKposHzdWbo7FgEkDBviWlYx++53XtQ33kvDU8dHAJm6L8usdwEZn09S3qiPed5lcCSLUpI0eEA8620zLbDl6bh8T+egkI+/7Rl6kegcTSPst1QUKaM+brhrjnF2yUQJNxnrGMnR7KbTw5nYFVjyAl98w2+VdvVlA67Dw3BgROjAKa+yyrpz0BKTbJnez1NT6AKrrnA1bEi1av2v3xaiL90dnxL2Kc0rsXc4WpcQEc8AEtiGrRiejmK6WWeMDIxtVwwKExijB5KFuBYIg1cy8dx0dTQ/yQVc78yBXMIqJ5i/VvvkqHdSjXuM/THKy7w2LQJ6fpJms38QiHGvlzBt+RwJv2JQ2elbjyRtjIi1AIRMAsKPuQduHVzr2YW+kIBE5BTwOzzxLKOiMX8QVuWh00IpqD+S0WHtLlzefpLBOZo/IYvEqQPnTX5dxmy4xookqaCjRuT4mMi8g3bxs2KCkj3GFj4+QSzA0RkeskU8iCJeUiBDv09Jt8OPEV6k7DlP3gxxh/dAPymPh/Kf5d897dIOd9P7H8oEd4G1JV8wPGbRadx52sgLmrRAZ99EZ5+LZgV+v+4Llrg/wX6HRCxgvzAAwAAAABJRU5ErkJggg==")}}.edd-text-loading{animation:skeleton-loading 1s infinite alternate}.edd-button__toggle{position:relative;padding:0;width:36px;height:20px;min-height:unset;transition:background .2s ease;border-radius:30px;box-shadow:none;border:none;display:flex;justify-content:center;align-items:center;background:#c3c4c7;margin:0 12px 0 0}.edd-button__toggle--enabled{background:var(--wp-admin-theme-color)}.edd-button__toggle--enabled:after{transform:translateX(16px)}.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%;background:#fff}.edd-stripe-payment-methods__description .button-group{display:block!important;margin:20px auto;text-align:center}.edd_settingsstripe_payment_methods{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1em}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap{background-color:#fff;border:1px solid #c3c4c7;border-radius:4px;padding:20px;margin:0;justify-content:space-between}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .edd-toggle{gap:8px;margin-bottom:0}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .edd-form-group__control__label{flex-grow:1;display:flex;align-items:center;gap:8px;justify-content:space-between}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .edd-form-group__control__label label{flex-grow:1}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .description{flex-basis:100%}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap .edd-icon__placeholder,.edd_settingsstripe_payment_methods .edd-form-group__control--wrap>svg{width:32px;height:32px}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap input[type=checkbox]{order:100}.edd_settingsstripe_payment_methods .edd-form-group__control--wrap input[type=checkbox]:disabled:not(:checked){background-color:rgba(214,54,56,.5)} \ No newline at end of file diff --git a/assets/css/stripe-cardelements-rtl.min.css b/assets/css/stripe-cardelements-rtl.min.css new file mode 100644 index 00000000000..6979ea0eab5 --- /dev/null +++ b/assets/css/stripe-cardelements-rtl.min.css @@ -0,0 +1 @@ +#edd-stripe-card-errors:not(:empty){margin:20px 0 0}:root{--edds-modal-grid-unit:1rem;--edds-modal-overlay:rgba(0,0,0,0.6)}.edds-modal__overlay{z-index:9999;position:fixed;top:0;right:0;left:0;bottom:0;background:rgba(0,0,0,.6);background:var(--edds-modal-overlay);display:flex;justify-content:center;align-items:center}.edds-modal__container{background-color:#fff;min-width:350px;max-width:90vw;max-height:90vh;box-sizing:border-box;overflow-y:auto}.admin-bar .edds-modal__container{margin-top:32px}.edds-modal__header{padding:1.5rem;padding:calc(var(--edds-modal-grid-unit)*1.5);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:2;background:#fff;border-bottom:1px solid #eee}.edds-modal__title{text-align:right;font-size:150%;margin:0}.edds-modal__close{line-height:1;padding:1rem}.edds-modal__close:before{content:"✕"}.edds-modal__content{margin:1.5rem;margin:calc(var(--edds-modal-grid-unit)*1.5)}@keyframes eddsSlideIn{0%{transform:translateY(15%)}to{transform:translateY(0)}}@keyframes eddsSlideOut{0%{transform:translateY(0)}to{transform:translateY(15%)}}.edds-modal.has-slide{display:none}.edds-modal.has-slide.is-open{display:block}.edds-modal.has-slide[aria-hidden=false] .edds-modal__container{animation:eddsSlideIn .3s cubic-bezier(0,0,.2,1)}.edds-modal.has-slide[aria-hidden=true] .edds-modal__container{animation:eddsSlideOut .3s cubic-bezier(0,0,.2,1)}.edds-modal.has-slide .edds-modal__container,.edds-modal.has-slide .edds-modal__overlay{will-change:transform}.edds-prb{margin:15px 0;display:none}.edds-prb__or{font-size:90%;text-align:center;margin:15px 0;overflow:hidden}.edds-prb__or:after,.edds-prb__or:before{background-color:rgba(0,0,0,.1);content:"";display:inline-block;height:1px;position:relative;vertical-align:middle;width:50%}.edds-prb__or:before{left:.5em;margin-right:-50%}.edds-prb__or:after{right:.5em;margin-left:-50%}.edd_download_purchase_form.loading{position:relative}.edd_download_purchase_form.loading:after{content:"";position:absolute;right:0;width:100%;height:100%;top:0;z-index:100}.edd_download_purchase_form.loading>*{opacity:.65}#edd_checkout_form_wrap.loading{position:relative}#edd_checkout_form_wrap.loading:after{content:"";position:absolute;right:0;width:100%;height:100%;top:0;z-index:100}#edd_checkout_form_wrap.loading>*{opacity:.65}#edd_checkout_form_wrap:not(.edd-prb--is-active) #edd-payment-mode-wrap #edd-gateway-option-stripe-prb{display:none!important}#edd_checkout_form_wrap .edds-prb{margin-bottom:0}#edd_checkout_form_wrap .edds-prb__or{display:none}#edd_checkout_form_wrap .edd-card-selector-radio label{display:inline;vertical-align:middle;font-weight:400;line-height:24px;font-size:100%;margin:0}.edd-card-selector-radio .edd-stripe-card-radio-item{width:100%;font-size:15px;padding:8px 12px;border:1px solid transparent}.edd-card-selector-radio .edd-stripe-card-radio-item label{line-height:1;margin:0;display:flex;align-items:center}.edd-card-selector-radio .edd-stripe-card-radio-item label .add-new-card,.edd-card-selector-radio .edd-stripe-card-radio-item label .card-label{margin-right:8px}.edd-card-selector-radio .edd-stripe-card-radio-item.selected{border:1px solid #f0f0f0;background-color:#fcfcfc;border-radius:3px}.edd-card-selector-radio div.card-label{width:65%;display:inline-block}.edd-card-selector-radio span.card-is-default{font-style:italic}.edd-card-selector-radio span.card-expired{color:#f33}.edd-stripe-card-selector+.edd-stripe-new-card{margin-top:20px}#edd-stripe-manage-cards .edd-stripe-add-new-card{margin:20px 0 10px}#edd-stripe-manage-cards div.edd-stripe-card-item{list-style:none;width:100%;display:-ms-inline-grid;display:inline-grid;border:1px solid #f0f0f0;padding:5px 10px;min-height:100px;margin-bottom:10px;border-radius:3px;margin-right:0}.edd-stripe-card-item>span{display:block}.edd-stripe-card-item .card-meta>span{color:#999;display:block}.edd-stripe-card-item .card-actions a{text-decoration:none}.edd-stripe-card-item .card-actions a.delete{color:#f33}.card-actions .edd-loading-ajax{display:inline-block;margin-left:4px}.edd-stripe-card-item .card-update-form{display:none}.edd-stripe-card-item .card-update-form label{font-weight:700}.edd-stripe-card-item .card-update-form input{margin-bottom:3px}.edd-stripe-card-item .card-update-form select{background:#fff;border:1px solid #e4e4e4;height:40px;margin-left:3px}.edd-stripe-card-item .card-update-form select:first-of-type{margin-left:3px}.edd-stripe-card-item .card-address-fields input,.edd-stripe-card-item .card-address-fields select{width:49%;display:inline-block}.edd-stripe-add-new-card label{font-weight:700;display:block;position:relative;line-height:100%;font-size:95%;margin:0 0 5px}.edd-stripe-add-new-card label:after{display:block;visibility:hidden;float:none;clear:both;height:0;text-indent:-9999px;content:"."}.edd-stripe-add-new-card span.edd-description{color:#666;font-size:80%;display:block;margin:0 0 5px}.edd-stripe-add-new-card input.edd-input,.edd-stripe-add-new-card textarea.edd-input{display:inline-block;width:70%}.edd-stripe-add-new-card select.edd-select{display:block;width:60%}.edd-stripe-add-new-card select.edd-select.edd-select-small{display:inline;width:auto}.edd-stripe-add-new-card input.edd-input.error,.edd-stripe-add-new-card textarea.edd-input.error{border-color:#c4554e}.edd-stripe-add-new-card>p{margin:0 0 21px}.edd-stripe-add-new-card span.edd-required-indicator{color:#b94a48;display:inline}.edd-stripe-add-new-card input[type=email],.edd-stripe-add-new-card input[type=password],.edd-stripe-add-new-card input[type=tel],.edd-stripe-add-new-card input[type=text],.edd-stripe-add-new-card textarea{padding:4px 6px}.edd-stripe-add-new-card input[type=radio]{border:none;margin-left:5px}.edd-stripe-add-new-card input[type=checkbox]{display:inline-block;margin:0 0 0 5px}.edd-stripe-add-new-card input[type=checkbox]+label,.edd-stripe-add-new-card input[type=checkbox]+label:after{display:inline}.edd-stripe-add-new-card .edd-payment-icons{height:32px;display:block;margin:0 0 8px}.edd-stripe-add-new-card .edd-payment-icons img.payment-icon{max-height:32px;width:auto;margin:0 0 0 3px;float:right;background:none;padding:0;border:none;box-shadow:none}.edd-stripe-add-new-card #edd-payment-mode-wrap label{display:inline-block;margin:0 0 0 20px}.edd-stripe-add-new-card #edd-payment-mode-wrap .edd-payment-mode-label{font-weight:700;display:inline-block;position:relative;margin-bottom:5px}.edd-stripe-add-new-card fieldset{border:1px solid #eee;padding:1.387em;margin:0 0 21px}.edd-stripe-add-new-card #edd_discount_code,.edd-stripe-add-new-card #edd_purchase_submit,.edd-stripe-add-new-card #edd_register_account_fields{padding:0;border:none}.edd-stripe-add-new-card fieldset fieldset{margin:0;border:none;padding:0}.edd-stripe-add-new-card #edd-login-account-wrap,.edd-stripe-add-new-card #edd-new-account-wrap,.edd-stripe-add-new-card #edd_final_total_wrap,.edd-stripe-add-new-card #edd_show_discount,.edd-stripe-add-new-card .edd-cart-adjustment{background:#fafafa;color:#666;padding:.5em 1.387em}.edd-stripe-add-new-card #edd-discount-code-wrap,.edd-stripe-add-new-card #edd_final_total_wrap,.edd-stripe-add-new-card #edd_show_discount{border:1px solid #eee}.edd-stripe-add-new-card .edd-cart-adjustment{padding:1.387em}.edd-stripe-add-new-card .edd-cart-adjustment input.edd-input,.edd-stripe-add-new-card .edd-cart-adjustment input.edd-submit{display:inline-block}.edd-stripe-add-new-card .edd-cart-adjustment input.edd-submit{padding:3px 12px;margin-bottom:2px}.edd-stripe-add-new-card #edd-discount-error-wrap{width:100%;display:inline-block;margin:1em 0 0}.edd-stripe-add-new-card #edd-login-account-wrap,.edd-stripe-add-new-card #edd-new-account-wrap{margin:-1.387em -1.387em 21px;border-right:none;border-left:none;border-top:none}.edd-stripe-add-new-card #edd_payment_mode_select,.edd-stripe-add-new-card fieldset#edd_register_fields #edd_checkout_user_info{margin-bottom:21px}.edd-stripe-add-new-card fieldset#edd_register_account_fields legend{padding-top:11px}.edd-stripe-add-new-card fieldset#edd_register_account_fields p.edd_login_password,.edd-stripe-add-new-card fieldset#edd_register_account_fields p.edd_register_password{margin:0}.edd-stripe-add-new-card fieldset#edd_cc_fields{border:1px solid #f0f0f0;background:#f9f9f9;position:relative}.edd-stripe-add-new-card fieldset#edd_cc_fields legend{border:none;padding:0}.edd-stripe-add-new-card fieldset p:last-child{margin-bottom:0}#edd_secure_site_wrapper{padding:4px 0 4px 4px;font-weight:700}.edd-stripe-add-new-card span.exp-divider{display:inline}.edd-stripe-card-cvc-element.StripeElement,.edd-stripe-card-element.StripeElement,.edd-stripe-card-exp-element.StripeElement{box-sizing:border-box;padding:10px 12px;border:1px solid #ccc;background-color:#fff}.edd-stripe-card-element.StripeElement--invalid{border-color:#c4554e!important}#edd-card-wrap{position:relative}#edd-card-details-wrap{display:flex;justify-content:space-between;flex-wrap:wrap}#edd-card-details-wrap p:empty{width:100%}#edd-card-cvv-wrap,#edd-card-exp-wrap{width:48%}#edd-stripe-card-element-wrapper{position:relative}#edd_checkout_form_wrap .edd-stripe-new-card span.card-type{background-size:32px 24px!important;width:32px;height:24px;top:50%;transform:translate3d(0,-50%,0);left:10px}.edds-buy-now-modal{width:500px}.edds-buy-now-modal .edds-modal__close{padding:.5rem}.edds-buy-now-modal #edd_checkout_form_wrap input.edd-input,.edds-buy-now-modal #edd_checkout_form_wrap textarea.edd-input{width:100%}.edds-buy-now-modal #edd_checkout_form_wrap #edd_purchase_submit{margin-top:1.5rem;margin-bottom:0}.edds-buy-now-modal .edds-field-spacer-shim{margin-bottom:1rem}.edds-buy-now-modal .edd-alert-error{margin:20px 0}.edds-buy-now-modal #edd-stripe-card-errors:not(:empty){margin-bottom:20px}.edds-buy-now-modal #edd-stripe-card-errors:not(:empty) .edd-alert-error{margin:0} \ No newline at end of file diff --git a/assets/css/stripe-cardelements.min.css b/assets/css/stripe-cardelements.min.css new file mode 100644 index 00000000000..cfb6a37170f --- /dev/null +++ b/assets/css/stripe-cardelements.min.css @@ -0,0 +1 @@ +#edd-stripe-card-errors:not(:empty){margin:20px 0 0}:root{--edds-modal-grid-unit:1rem;--edds-modal-overlay:rgba(0,0,0,0.6)}.edds-modal__overlay{z-index:9999;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);background:var(--edds-modal-overlay);display:flex;justify-content:center;align-items:center}.edds-modal__container{background-color:#fff;min-width:350px;max-width:90vw;max-height:90vh;box-sizing:border-box;overflow-y:auto}.admin-bar .edds-modal__container{margin-top:32px}.edds-modal__header{padding:1.5rem;padding:calc(var(--edds-modal-grid-unit)*1.5);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:2;background:#fff;border-bottom:1px solid #eee}.edds-modal__title{text-align:left;font-size:150%;margin:0}.edds-modal__close{line-height:1;padding:1rem}.edds-modal__close:before{content:"✕"}.edds-modal__content{margin:1.5rem;margin:calc(var(--edds-modal-grid-unit)*1.5)}@keyframes eddsSlideIn{0%{transform:translateY(15%)}to{transform:translateY(0)}}@keyframes eddsSlideOut{0%{transform:translateY(0)}to{transform:translateY(15%)}}.edds-modal.has-slide{display:none}.edds-modal.has-slide.is-open{display:block}.edds-modal.has-slide[aria-hidden=false] .edds-modal__container{animation:eddsSlideIn .3s cubic-bezier(0,0,.2,1)}.edds-modal.has-slide[aria-hidden=true] .edds-modal__container{animation:eddsSlideOut .3s cubic-bezier(0,0,.2,1)}.edds-modal.has-slide .edds-modal__container,.edds-modal.has-slide .edds-modal__overlay{will-change:transform}.edds-prb{margin:15px 0;display:none}.edds-prb__or{font-size:90%;text-align:center;margin:15px 0;overflow:hidden}.edds-prb__or:after,.edds-prb__or:before{background-color:rgba(0,0,0,.1);content:"";display:inline-block;height:1px;position:relative;vertical-align:middle;width:50%}.edds-prb__or:before{right:.5em;margin-left:-50%}.edds-prb__or:after{left:.5em;margin-right:-50%}.edd_download_purchase_form.loading{position:relative}.edd_download_purchase_form.loading:after{content:"";position:absolute;left:0;width:100%;height:100%;top:0;z-index:100}.edd_download_purchase_form.loading>*{opacity:.65}#edd_checkout_form_wrap.loading{position:relative}#edd_checkout_form_wrap.loading:after{content:"";position:absolute;left:0;width:100%;height:100%;top:0;z-index:100}#edd_checkout_form_wrap.loading>*{opacity:.65}#edd_checkout_form_wrap:not(.edd-prb--is-active) #edd-payment-mode-wrap #edd-gateway-option-stripe-prb{display:none!important}#edd_checkout_form_wrap .edds-prb{margin-bottom:0}#edd_checkout_form_wrap .edds-prb__or{display:none}#edd_checkout_form_wrap .edd-card-selector-radio label{display:inline;vertical-align:middle;font-weight:400;line-height:24px;font-size:100%;margin:0}.edd-card-selector-radio .edd-stripe-card-radio-item{width:100%;font-size:15px;padding:8px 12px;border:1px solid transparent}.edd-card-selector-radio .edd-stripe-card-radio-item label{line-height:1;margin:0;display:flex;align-items:center}.edd-card-selector-radio .edd-stripe-card-radio-item label .add-new-card,.edd-card-selector-radio .edd-stripe-card-radio-item label .card-label{margin-left:8px}.edd-card-selector-radio .edd-stripe-card-radio-item.selected{border:1px solid #f0f0f0;background-color:#fcfcfc;border-radius:3px}.edd-card-selector-radio div.card-label{width:65%;display:inline-block}.edd-card-selector-radio span.card-is-default{font-style:italic}.edd-card-selector-radio span.card-expired{color:#f33}.edd-stripe-card-selector+.edd-stripe-new-card{margin-top:20px}#edd-stripe-manage-cards .edd-stripe-add-new-card{margin:20px 0 10px}#edd-stripe-manage-cards div.edd-stripe-card-item{list-style:none;width:100%;display:-ms-inline-grid;display:inline-grid;border:1px solid #f0f0f0;padding:5px 10px;min-height:100px;margin-bottom:10px;border-radius:3px;margin-left:0}.edd-stripe-card-item>span{display:block}.edd-stripe-card-item .card-meta>span{color:#999;display:block}.edd-stripe-card-item .card-actions a{text-decoration:none}.edd-stripe-card-item .card-actions a.delete{color:#f33}.card-actions .edd-loading-ajax{display:inline-block;margin-right:4px}.edd-stripe-card-item .card-update-form{display:none}.edd-stripe-card-item .card-update-form label{font-weight:700}.edd-stripe-card-item .card-update-form input{margin-bottom:3px}.edd-stripe-card-item .card-update-form select{background:#fff;border:1px solid #e4e4e4;height:40px;margin-right:3px}.edd-stripe-card-item .card-update-form select:first-of-type{margin-right:3px}.edd-stripe-card-item .card-address-fields input,.edd-stripe-card-item .card-address-fields select{width:49%;display:inline-block}.edd-stripe-add-new-card label{font-weight:700;display:block;position:relative;line-height:100%;font-size:95%;margin:0 0 5px}.edd-stripe-add-new-card label:after{display:block;visibility:hidden;float:none;clear:both;height:0;text-indent:-9999px;content:"."}.edd-stripe-add-new-card span.edd-description{color:#666;font-size:80%;display:block;margin:0 0 5px}.edd-stripe-add-new-card input.edd-input,.edd-stripe-add-new-card textarea.edd-input{display:inline-block;width:70%}.edd-stripe-add-new-card select.edd-select{display:block;width:60%}.edd-stripe-add-new-card select.edd-select.edd-select-small{display:inline;width:auto}.edd-stripe-add-new-card input.edd-input.error,.edd-stripe-add-new-card textarea.edd-input.error{border-color:#c4554e}.edd-stripe-add-new-card>p{margin:0 0 21px}.edd-stripe-add-new-card span.edd-required-indicator{color:#b94a48;display:inline}.edd-stripe-add-new-card input[type=email],.edd-stripe-add-new-card input[type=password],.edd-stripe-add-new-card input[type=tel],.edd-stripe-add-new-card input[type=text],.edd-stripe-add-new-card textarea{padding:4px 6px}.edd-stripe-add-new-card input[type=radio]{border:none;margin-right:5px}.edd-stripe-add-new-card input[type=checkbox]{display:inline-block;margin:0 5px 0 0}.edd-stripe-add-new-card input[type=checkbox]+label,.edd-stripe-add-new-card input[type=checkbox]+label:after{display:inline}.edd-stripe-add-new-card .edd-payment-icons{height:32px;display:block;margin:0 0 8px}.edd-stripe-add-new-card .edd-payment-icons img.payment-icon{max-height:32px;width:auto;margin:0 3px 0 0;float:left;background:none;padding:0;border:none;box-shadow:none}.edd-stripe-add-new-card #edd-payment-mode-wrap label{display:inline-block;margin:0 20px 0 0}.edd-stripe-add-new-card #edd-payment-mode-wrap .edd-payment-mode-label{font-weight:700;display:inline-block;position:relative;margin-bottom:5px}.edd-stripe-add-new-card fieldset{border:1px solid #eee;padding:1.387em;margin:0 0 21px}.edd-stripe-add-new-card #edd_discount_code,.edd-stripe-add-new-card #edd_purchase_submit,.edd-stripe-add-new-card #edd_register_account_fields{padding:0;border:none}.edd-stripe-add-new-card fieldset fieldset{margin:0;border:none;padding:0}.edd-stripe-add-new-card #edd-login-account-wrap,.edd-stripe-add-new-card #edd-new-account-wrap,.edd-stripe-add-new-card #edd_final_total_wrap,.edd-stripe-add-new-card #edd_show_discount,.edd-stripe-add-new-card .edd-cart-adjustment{background:#fafafa;color:#666;padding:.5em 1.387em}.edd-stripe-add-new-card #edd-discount-code-wrap,.edd-stripe-add-new-card #edd_final_total_wrap,.edd-stripe-add-new-card #edd_show_discount{border:1px solid #eee}.edd-stripe-add-new-card .edd-cart-adjustment{padding:1.387em}.edd-stripe-add-new-card .edd-cart-adjustment input.edd-input,.edd-stripe-add-new-card .edd-cart-adjustment input.edd-submit{display:inline-block}.edd-stripe-add-new-card .edd-cart-adjustment input.edd-submit{padding:3px 12px;margin-bottom:2px}.edd-stripe-add-new-card #edd-discount-error-wrap{width:100%;display:inline-block;margin:1em 0 0}.edd-stripe-add-new-card #edd-login-account-wrap,.edd-stripe-add-new-card #edd-new-account-wrap{margin:-1.387em -1.387em 21px;border-left:none;border-right:none;border-top:none}.edd-stripe-add-new-card #edd_payment_mode_select,.edd-stripe-add-new-card fieldset#edd_register_fields #edd_checkout_user_info{margin-bottom:21px}.edd-stripe-add-new-card fieldset#edd_register_account_fields legend{padding-top:11px}.edd-stripe-add-new-card fieldset#edd_register_account_fields p.edd_login_password,.edd-stripe-add-new-card fieldset#edd_register_account_fields p.edd_register_password{margin:0}.edd-stripe-add-new-card fieldset#edd_cc_fields{border:1px solid #f0f0f0;background:#f9f9f9;position:relative}.edd-stripe-add-new-card fieldset#edd_cc_fields legend{border:none;padding:0}.edd-stripe-add-new-card fieldset p:last-child{margin-bottom:0}#edd_secure_site_wrapper{padding:4px 4px 4px 0;font-weight:700}.edd-stripe-add-new-card span.exp-divider{display:inline}.edd-stripe-card-cvc-element.StripeElement,.edd-stripe-card-element.StripeElement,.edd-stripe-card-exp-element.StripeElement{box-sizing:border-box;padding:10px 12px;border:1px solid #ccc;background-color:#fff}.edd-stripe-card-element.StripeElement--invalid{border-color:#c4554e!important}#edd-card-wrap{position:relative}#edd-card-details-wrap{display:flex;justify-content:space-between;flex-wrap:wrap}#edd-card-details-wrap p:empty{width:100%}#edd-card-cvv-wrap,#edd-card-exp-wrap{width:48%}#edd-stripe-card-element-wrapper{position:relative}#edd_checkout_form_wrap .edd-stripe-new-card span.card-type{background-size:32px 24px!important;width:32px;height:24px;top:50%;transform:translate3d(0,-50%,0);right:10px}.edds-buy-now-modal{width:500px}.edds-buy-now-modal .edds-modal__close{padding:.5rem}.edds-buy-now-modal #edd_checkout_form_wrap input.edd-input,.edds-buy-now-modal #edd_checkout_form_wrap textarea.edd-input{width:100%}.edds-buy-now-modal #edd_checkout_form_wrap #edd_purchase_submit{margin-top:1.5rem;margin-bottom:0}.edds-buy-now-modal .edds-field-spacer-shim{margin-bottom:1rem}.edds-buy-now-modal .edd-alert-error{margin:20px 0}.edds-buy-now-modal #edd-stripe-card-errors:not(:empty){margin-bottom:20px}.edds-buy-now-modal #edd-stripe-card-errors:not(:empty) .edd-alert-error{margin:0} \ No newline at end of file diff --git a/assets/css/stripe-paymentelements-rtl.min.css b/assets/css/stripe-paymentelements-rtl.min.css new file mode 100644 index 00000000000..e6e315ae44f --- /dev/null +++ b/assets/css/stripe-paymentelements-rtl.min.css @@ -0,0 +1 @@ +#edd-stripe-card-errors:not(:empty){margin:20px 0 0}:root{--edds-modal-grid-unit:1rem;--edds-modal-overlay:rgba(0,0,0,0.6)}.edds-modal__overlay{z-index:9999;position:fixed;top:0;right:0;left:0;bottom:0;background:rgba(0,0,0,.6);background:var(--edds-modal-overlay);display:flex;justify-content:center;align-items:center}.edds-modal__container{background-color:#fff;min-width:350px;max-width:90vw;max-height:90vh;box-sizing:border-box;overflow-y:auto}.admin-bar .edds-modal__container{margin-top:32px}.edds-modal__header{padding:1.5rem;padding:calc(var(--edds-modal-grid-unit)*1.5);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:2;background:#fff;border-bottom:1px solid #eee}.edds-modal__title{text-align:right;font-size:150%;margin:0}.edds-modal__close{line-height:1;padding:1rem}.edds-modal__close:before{content:"✕"}.edds-modal__content{margin:1.5rem;margin:calc(var(--edds-modal-grid-unit)*1.5)}@keyframes eddsSlideIn{0%{transform:translateY(15%)}to{transform:translateY(0)}}@keyframes eddsSlideOut{0%{transform:translateY(0)}to{transform:translateY(15%)}}.edds-modal.has-slide{display:none}.edds-modal.has-slide.is-open{display:block}.edds-modal.has-slide[aria-hidden=false] .edds-modal__container{animation:eddsSlideIn .3s cubic-bezier(0,0,.2,1)}.edds-modal.has-slide[aria-hidden=true] .edds-modal__container{animation:eddsSlideOut .3s cubic-bezier(0,0,.2,1)}.edds-modal.has-slide .edds-modal__container,.edds-modal.has-slide .edds-modal__overlay{will-change:transform}.edds-buy-now-modal{width:500px}.edds-buy-now-modal .edds-modal__close{padding:.5rem}.edds-buy-now-modal #edd_checkout_form_wrap input.edd-input,.edds-buy-now-modal #edd_checkout_form_wrap textarea.edd-input{width:100%}.edds-buy-now-modal #edd_checkout_form_wrap #edd_purchase_submit{margin-top:1.5rem;margin-bottom:0}.edds-buy-now-modal .edds-field-spacer-shim{margin-bottom:1rem}.edds-buy-now-modal .edd-alert-error{margin:20px 0}.edds-buy-now-modal #edd-stripe-card-errors:not(:empty){margin-bottom:20px}.edds-buy-now-modal #edd-stripe-card-errors:not(:empty) .edd-alert-error{margin:0}#edd_purchase_submit #edd-purchase-button[data-edd-button-state=disabled]{opacity:.5;cursor:not-allowed}#edd_purchase_submit #edd-purchase-button [data-edd-button-state=processing],#edd_purchase_submit #edd-purchase-button[data-edd-button-state=updating]{opacity:.5;cursor:wait}#card_name,.card-name{padding:8px!important;width:100%!important;box-sizing:border-box} \ No newline at end of file diff --git a/assets/css/stripe-paymentelements.min.css b/assets/css/stripe-paymentelements.min.css new file mode 100644 index 00000000000..dcd91e16d5b --- /dev/null +++ b/assets/css/stripe-paymentelements.min.css @@ -0,0 +1 @@ +#edd-stripe-card-errors:not(:empty){margin:20px 0 0}:root{--edds-modal-grid-unit:1rem;--edds-modal-overlay:rgba(0,0,0,0.6)}.edds-modal__overlay{z-index:9999;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);background:var(--edds-modal-overlay);display:flex;justify-content:center;align-items:center}.edds-modal__container{background-color:#fff;min-width:350px;max-width:90vw;max-height:90vh;box-sizing:border-box;overflow-y:auto}.admin-bar .edds-modal__container{margin-top:32px}.edds-modal__header{padding:1.5rem;padding:calc(var(--edds-modal-grid-unit)*1.5);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:2;background:#fff;border-bottom:1px solid #eee}.edds-modal__title{text-align:left;font-size:150%;margin:0}.edds-modal__close{line-height:1;padding:1rem}.edds-modal__close:before{content:"✕"}.edds-modal__content{margin:1.5rem;margin:calc(var(--edds-modal-grid-unit)*1.5)}@keyframes eddsSlideIn{0%{transform:translateY(15%)}to{transform:translateY(0)}}@keyframes eddsSlideOut{0%{transform:translateY(0)}to{transform:translateY(15%)}}.edds-modal.has-slide{display:none}.edds-modal.has-slide.is-open{display:block}.edds-modal.has-slide[aria-hidden=false] .edds-modal__container{animation:eddsSlideIn .3s cubic-bezier(0,0,.2,1)}.edds-modal.has-slide[aria-hidden=true] .edds-modal__container{animation:eddsSlideOut .3s cubic-bezier(0,0,.2,1)}.edds-modal.has-slide .edds-modal__container,.edds-modal.has-slide .edds-modal__overlay{will-change:transform}.edds-buy-now-modal{width:500px}.edds-buy-now-modal .edds-modal__close{padding:.5rem}.edds-buy-now-modal #edd_checkout_form_wrap input.edd-input,.edds-buy-now-modal #edd_checkout_form_wrap textarea.edd-input{width:100%}.edds-buy-now-modal #edd_checkout_form_wrap #edd_purchase_submit{margin-top:1.5rem;margin-bottom:0}.edds-buy-now-modal .edds-field-spacer-shim{margin-bottom:1rem}.edds-buy-now-modal .edd-alert-error{margin:20px 0}.edds-buy-now-modal #edd-stripe-card-errors:not(:empty){margin-bottom:20px}.edds-buy-now-modal #edd-stripe-card-errors:not(:empty) .edd-alert-error{margin:0}#edd_purchase_submit #edd-purchase-button[data-edd-button-state=disabled]{opacity:.5;cursor:not-allowed}#edd_purchase_submit #edd-purchase-button [data-edd-button-state=processing],#edd_purchase_submit #edd-purchase-button[data-edd-button-state=updating]{opacity:.5;cursor:wait}#card_name,.card-name{padding:8px!important;width:100%!important;box-sizing:border-box} \ No newline at end of file diff --git a/assets/css/variables/_animations.scss b/assets/css/variables/_animations.scss new file mode 100644 index 00000000000..34de63b1bf9 --- /dev/null +++ b/assets/css/variables/_animations.scss @@ -0,0 +1,8 @@ +@keyframes skeleton-loading { + 0% { + background-color: hsl(210, 20%, 85%); + } + 100% { + background-color: hsl(210, 20%, 90%); + } +} diff --git a/assets/css/variables/_colors.scss b/assets/css/variables/_colors.scss new file mode 100644 index 00000000000..be82aebad42 --- /dev/null +++ b/assets/css/variables/_colors.scss @@ -0,0 +1,97 @@ +@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-input-border-2: #8c8f94; + +$wp-alternate: #f9f9f9; +$wp-inactive: #999; + +$wp-red-50: #d63638; +$wp-green-30: #00ba37; +$wp-green-50: #008a20; +$wp-yellow-50: #996800; +$warning: #f18200; + +$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, +); + +/** + * EDD Brand Colors + */ +$edd-blue-gray: #35495c; // Found on our brand assets page. +$edd-light-blue: #2794da; // Found on our brand assets page. +$edd-light-gray: #f4f7fa; // Found on our brand assets page. + +/** + * EDD Admin Colors + */ + +/** + * Used in: + * Admin bar thin line. + * Persistent dismissables. + * Flyout item backgrounds. + */ +$edd-notice-blue: #0c5d95; +$edd-notice-blue-hover: $edd-blue-gray; + +/** + * Used in: + * Persistent dismissables - Alerts + * Flyout item backgrounds - Activate License key + * Flyout bubble - Alert Icon. + */ +$edd-alert-red: #d63638; // The red used in our notice bars and the flyout items. +$edd-alert-red-hover: #b60012; // The hover color for the alert red. + +/** + * Used in: + * Flyout item backgrounds - Upgrade to Pro/Get a License + * Upgrade to Pro links + */ +$edd-notice-green: #1da867; +$edd-notice-green-hover: #199155; + +$edd-very-light-gray: #E8E8E8; // A light gray used for box shadows in some section content that overlays the WP core background. + +/** + * Used In: + * Social Links in our footer. + */ +$edd-footer-social-link: #A7AAAD; +$edd-footer-social-link-hover: #50575e; + +/** + * Supplemental colors that WP Core Is Missing: + */ +$gray-800: #494949; + +/** + * Upgrade to Pro Colors + */ +$edd-pro-upgrade: #1da867; + +/** + * EDD Tooltips + */ +$edd-tool-tip-icon-color: rgba(126, 137, 147, 1); +$edd-tooltip-background: $white; +$edd-tooltip-text: #23282D; +$edd-tool-tip-shadow: 0px 8px 36px 0px rgba(29, 36, 40, 0.15); 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..eab8b1c7e46 --- /dev/null +++ b/assets/css/variables/_mixins.scss @@ -0,0 +1,93 @@ +@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; +} + + +@mixin edd-popup( $width: 300px, $height: auto, $transformY: 0, $transformX: 0, $pointer-size: 10px, $pointer-position: bottom ) { + + $box-shadow-y: if( $pointer-position == bottom, 2px, -2px ); + + position: absolute; + z-index: 99; + width: $width; + height: $height; + margin: auto; + padding: 10px; + transform: translate( $transformX, $transformY ); + background-color: #fff; + border: 1px solid $wp-input-border-2; + border-radius: 4px; + box-shadow: 0 $box-shadow-y 5px 0 rgba(0,0,0,.25); + box-sizing: border-box; + display: none; + + &::after { + content: ""; + width: $pointer-size; + height: $pointer-size; + background: #fff; + position: absolute; + margin: auto; + transform: rotate(45deg); + z-index: -1; + border-style: solid; + border-color: #8c8f94; + + $pointer-offset: calc( -1 * ( ( $pointer-size / 2 ) + 1px ) ); + + @if $pointer-position == bottom { + left: 0; + right: 0; + bottom: $pointer-offset; + border-width: 0 1px 1px 0; + } @else if $pointer-position == top { + left: 0; + right: 0; + top: $pointer-offset; + border-width: 1px 0 0 1px; + } @else if $pointer-position == right { + top: 0; + bottom: 0; + right: $pointer-offset; + border-width: 1px 1px 0 0; + } @else if $pointer-position == left { + top: 0; + bottom: 0; + left: $pointer-offset; + border-width: 0 0 1px 1px; + } + } +} + +@mixin screen-reader-text { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; +} diff --git a/assets/css/variables/_variables.scss b/assets/css/variables/_variables.scss new file mode 100644 index 00000000000..363d8d7f527 --- /dev/null +++ b/assets/css/variables/_variables.scss @@ -0,0 +1,10 @@ +@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"; +@import "animations"; 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/admin-flyout-menu/edd-active.svg b/assets/images/admin-flyout-menu/edd-active.svg new file mode 100644 index 00000000000..c7d08f5c21c --- /dev/null +++ b/assets/images/admin-flyout-menu/edd-active.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/admin-flyout-menu/edd-default.svg b/assets/images/admin-flyout-menu/edd-default.svg new file mode 100644 index 00000000000..988c3424b55 --- /dev/null +++ b/assets/images/admin-flyout-menu/edd-default.svg @@ -0,0 +1,10 @@ + + + + + + + + + + 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/edd-blue-checkmark.svg b/assets/images/icons/edd-blue-checkmark.svg new file mode 100644 index 00000000000..44ac69eebc6 --- /dev/null +++ b/assets/images/icons/edd-blue-checkmark.svg @@ -0,0 +1,4 @@ + + + + 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/about/configuring-caching.svg b/assets/images/promo/about/configuring-caching.svg new file mode 100644 index 00000000000..7e16111b3c6 --- /dev/null +++ b/assets/images/promo/about/configuring-caching.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/promo/about/getting-started-welcome.svg b/assets/images/promo/about/getting-started-welcome.svg new file mode 100644 index 00000000000..0f5c260c7a4 --- /dev/null +++ b/assets/images/promo/about/getting-started-welcome.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/promo/about/how-to-install-activate.svg b/assets/images/promo/about/how-to-install-activate.svg new file mode 100644 index 00000000000..ac8b9194656 --- /dev/null +++ b/assets/images/promo/about/how-to-install-activate.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/promo/about/introduction-to-edd.svg b/assets/images/promo/about/introduction-to-edd.svg new file mode 100644 index 00000000000..b188fc588c3 --- /dev/null +++ b/assets/images/promo/about/introduction-to-edd.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/promo/about/using-edd-blocks.svg b/assets/images/promo/about/using-edd-blocks.svg new file mode 100644 index 00000000000..47d84eb7113 --- /dev/null +++ b/assets/images/promo/about/using-edd-blocks.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/promo/am-team.jpg b/assets/images/promo/am-team.jpg new file mode 100644 index 00000000000..39bf5b60891 Binary files /dev/null and b/assets/images/promo/am-team.jpg differ 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/brands/plugin-affwp.png b/assets/images/promo/brands/plugin-affwp.png new file mode 100644 index 00000000000..cda0cc14bac Binary files /dev/null and b/assets/images/promo/brands/plugin-affwp.png differ diff --git a/assets/images/promo/brands/plugin-aioseo.png b/assets/images/promo/brands/plugin-aioseo.png new file mode 100644 index 00000000000..3636ad5709b Binary files /dev/null and b/assets/images/promo/brands/plugin-aioseo.png differ diff --git a/assets/images/promo/brands/plugin-charitable.png b/assets/images/promo/brands/plugin-charitable.png new file mode 100644 index 00000000000..ec16b9e3f2d Binary files /dev/null and b/assets/images/promo/brands/plugin-charitable.png differ diff --git a/assets/images/promo/brands/plugin-duplicator.png b/assets/images/promo/brands/plugin-duplicator.png new file mode 100644 index 00000000000..e405e571b1e Binary files /dev/null and b/assets/images/promo/brands/plugin-duplicator.png differ diff --git a/assets/images/promo/brands/plugin-edd.png b/assets/images/promo/brands/plugin-edd.png new file mode 100644 index 00000000000..69d4c267b75 Binary files /dev/null and b/assets/images/promo/brands/plugin-edd.png differ diff --git a/assets/images/promo/brands/plugin-mi.png b/assets/images/promo/brands/plugin-mi.png new file mode 100644 index 00000000000..b9e4beddbd9 Binary files /dev/null and b/assets/images/promo/brands/plugin-mi.png differ diff --git a/assets/images/promo/brands/plugin-om.png b/assets/images/promo/brands/plugin-om.png new file mode 100644 index 00000000000..4c1232e03be Binary files /dev/null and b/assets/images/promo/brands/plugin-om.png differ diff --git a/assets/images/promo/brands/plugin-pushengage.png b/assets/images/promo/brands/plugin-pushengage.png new file mode 100644 index 00000000000..4a92d9de04d Binary files /dev/null and b/assets/images/promo/brands/plugin-pushengage.png differ diff --git a/assets/images/promo/brands/plugin-rp.png b/assets/images/promo/brands/plugin-rp.png new file mode 100644 index 00000000000..2c85a21328e Binary files /dev/null and b/assets/images/promo/brands/plugin-rp.png differ diff --git a/assets/images/promo/brands/plugin-sb-fb.png b/assets/images/promo/brands/plugin-sb-fb.png new file mode 100644 index 00000000000..5f60be127c2 Binary files /dev/null and b/assets/images/promo/brands/plugin-sb-fb.png differ diff --git a/assets/images/promo/brands/plugin-sb-instagram.png b/assets/images/promo/brands/plugin-sb-instagram.png new file mode 100644 index 00000000000..2940cf59b68 Binary files /dev/null and b/assets/images/promo/brands/plugin-sb-instagram.png differ diff --git a/assets/images/promo/brands/plugin-sb-twitter.png b/assets/images/promo/brands/plugin-sb-twitter.png new file mode 100644 index 00000000000..a05b020eefb Binary files /dev/null and b/assets/images/promo/brands/plugin-sb-twitter.png differ diff --git a/assets/images/promo/brands/plugin-sb-youtube.png b/assets/images/promo/brands/plugin-sb-youtube.png new file mode 100644 index 00000000000..e9e36616c18 Binary files /dev/null and b/assets/images/promo/brands/plugin-sb-youtube.png differ diff --git a/assets/images/promo/brands/plugin-searchwp.png b/assets/images/promo/brands/plugin-searchwp.png new file mode 100644 index 00000000000..e0faa75de8d Binary files /dev/null and b/assets/images/promo/brands/plugin-searchwp.png differ diff --git a/assets/images/promo/brands/plugin-seedprod.png b/assets/images/promo/brands/plugin-seedprod.png new file mode 100644 index 00000000000..b6d7f30e54c Binary files /dev/null and b/assets/images/promo/brands/plugin-seedprod.png differ diff --git a/assets/images/promo/brands/plugin-smtp.png b/assets/images/promo/brands/plugin-smtp.png new file mode 100644 index 00000000000..adcccd2210a Binary files /dev/null and b/assets/images/promo/brands/plugin-smtp.png differ diff --git a/assets/images/promo/brands/plugin-sugarcalendar.png b/assets/images/promo/brands/plugin-sugarcalendar.png new file mode 100644 index 00000000000..b5b426e22cb Binary files /dev/null and b/assets/images/promo/brands/plugin-sugarcalendar.png differ diff --git a/assets/images/promo/brands/plugin-trustpulse.png b/assets/images/promo/brands/plugin-trustpulse.png new file mode 100644 index 00000000000..d91063e5184 Binary files /dev/null and b/assets/images/promo/brands/plugin-trustpulse.png differ diff --git a/assets/images/promo/brands/plugin-wp-simple-pay.png b/assets/images/promo/brands/plugin-wp-simple-pay.png new file mode 100644 index 00000000000..9d14d6a3410 Binary files /dev/null and b/assets/images/promo/brands/plugin-wp-simple-pay.png differ diff --git a/assets/images/promo/brands/plugin-wpcode.png b/assets/images/promo/brands/plugin-wpcode.png new file mode 100644 index 00000000000..92eac13b243 Binary files /dev/null and b/assets/images/promo/brands/plugin-wpcode.png differ diff --git a/assets/images/promo/brands/plugin-wpf.png b/assets/images/promo/brands/plugin-wpf.png new file mode 100644 index 00000000000..901e3f7762e Binary files /dev/null and b/assets/images/promo/brands/plugin-wpf.png differ 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/promo/icon-full.svg b/assets/images/promo/icon-full.svg new file mode 100644 index 00000000000..0865544d23a --- /dev/null +++ b/assets/images/promo/icon-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/promo/icon-none.svg b/assets/images/promo/icon-none.svg new file mode 100644 index 00000000000..9617560b0e9 --- /dev/null +++ b/assets/images/promo/icon-none.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/promo/icon-partial.svg b/assets/images/promo/icon-partial.svg new file mode 100644 index 00000000000..38881639a17 --- /dev/null +++ b/assets/images/promo/icon-partial.svg @@ -0,0 +1 @@ + \ No newline at end of file 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..4f3b2056c0a --- /dev/null +++ b/assets/js/admin/components/chosen/index.js @@ -0,0 +1,140 @@ +/* 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 = 521, + typingTimerElements = '.edd-select-chosen .chosen-search input, .edd-select-chosen .search-field input'; + + // 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' ), + current_id = container.hasClass( 'exclude-current' ) ? edd_vars.post_id : 0, + lastKey = e.which, + search_type = 'edd_download_search', + exclusions = select.data( 'excluded-products' ); + + // 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 ) || + [ 16, 13, 91, 17, 37, 38, 39, 40 ].includes( lastKey ) || + e.ctrlKey || + e.metaKey + ) { + 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, + current_id: current_id, + exclusions: exclusions, + }, + + 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 ) { + 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..e9e6f8bd109 --- /dev/null +++ b/assets/js/admin/components/location/index.js @@ -0,0 +1,60 @@ +import { getChosenVars } from 'utils/chosen.js'; + +jQuery( document ).ready( function ( $ ) { + $( '.edd_countries_filter' ).on( 'change', function () { + const select = $( this ), + state_field = $( '.edd_regions_filter' ), + data = { + action: 'edd_get_shop_states', + country: select.val(), + nonce: select.data( 'nonce' ), + field_name: state_field.attr( 'name' ), + field_id: state_field.attr( 'id' ), + field_classes: 'edd_regions_filter', + }; + + $.post( ajaxurl, data, function ( response ) { + + // hot fix for settings page + if ( $( 'body' ).hasClass( 'download_page_edd-settings' ) ) { + // only on these 2 scenarios we have to setup the field + if ( ( 'nostates' === response && state_field.is( 'select' ) ) || ( 'nostates' !== response && state_field.is( 'input' ) ) ) { + let attributes = {}; + $.each( + state_field.get(0)?.attributes || [], + ( i, attr ) => { + if ( ! [ 'style', 'type'].includes( attr.name ) ) { + attributes[ attr.name ] = attr.value; + } + } + ) + + const parent = state_field.parent(); + let newStateField = ''; + + if ( state_field.is( 'select' ) ) { + state_field.chosen( 'destroy' ); + newStateField = $( '' ).attr( { ...attributes, ...{ type: 'text', placeholder: edd_vars.enter_region } } ); + } else { + newStateField = $( response ).attr( { ...attributes, ...{ 'data-placeholder': edd_vars.select_region } } ).addClass( 'edd-select-chosen' ); + } + + state_field.remove(); + parent.prepend( newStateField ); + $( 'select.edd_regions_filter' ).chosen( { ...getChosenVars( newStateField ) } ); + return; + } + } + + $( '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/navigation/index.js b/assets/js/admin/components/navigation/index.js new file mode 100644 index 00000000000..501351c8b65 --- /dev/null +++ b/assets/js/admin/components/navigation/index.js @@ -0,0 +1,42 @@ +const adminPage = document.querySelector( '.edd-admin-page' ); +let navWrapper = document.querySelector( '.edd-nav__wrapper' ); + +if ( adminPage ) { + + if ( navWrapper ) { + // Move the subtitle inside the navWrapper. + const subtitle = document.querySelector( '.subtitle:not(.edd-search-query)' ); + if ( subtitle ) { + navWrapper.appendChild( subtitle ); + } + } + + // Move the notices after the navWrapper. + const adminNotices = document.querySelectorAll( '.notice:not(.inline)' ); + if ( adminNotices ) { + adminNotices.forEach( notice => { + // If the notice doesn't have the 'hidden' class, display it. + if ( !notice.classList.contains( 'hidden' ) ) { + notice.classList.add( 'edd-hidden' ); + } + } ); + setTimeout( () => { + if ( navWrapper ) { + const subNav = document.querySelector( '.edd-sub-nav__wrapper' ); + if ( subNav ) { + navWrapper = subNav; + } + const navWrapperParent = navWrapper.parentNode; + adminNotices.forEach( notice => { + navWrapperParent.insertBefore( notice, navWrapper.nextSibling ); + } ); + } + adminNotices.forEach( notice => { + // If the notice doesn't have the 'hidden' class, display it. + if ( ! notice.classList.contains( 'hidden' ) ) { + notice.classList.remove( 'edd-hidden' ); + } + } ); + }, 1000 ); + } +} diff --git a/assets/js/admin/components/promos/index.js b/assets/js/admin/components/promos/index.js new file mode 100644 index 00000000000..a0010f8e2d2 --- /dev/null +++ b/assets/js/admin/components/promos/index.js @@ -0,0 +1,112 @@ +/* global ajaxurl */ + +jQuery( document ).ready( function( $ ) { + + /** + * Show overlay notices on a delay. + */ + const overlayNotice = $( '.edd-admin-notice-overlay' ); + let overlayNoticeWrapper = $(); // empty jQuery object, so chaining still works + + if ( overlayNotice ) { + overlayNotice.wrap( '
' ); + overlayNoticeWrapper = overlayNotice.parent(); + + $( document ).on( 'click', '.edd-promo-notice__trigger', function () { + if ( $( this ).hasClass( 'edd-promo-notice__trigger--ajax' ) ) { + $.ajax( { + type: 'GET', + url: ajaxurl, + data: { + action: 'edd_get_promo_notice', + notice_id: $( this ).data( 'id' ), + product_id: $( this ).data( 'product' ), + value: $( this ).data( 'value' ), + }, + success: function ( response ) { + if ( response.data ) { + overlayNotice.html( response.data ); + // add a class to the overlay notice + overlayNoticeWrapper.addClass( 'edd-promo-notice__ajax' ); + } + triggerNoticeEnter( overlayNoticeWrapper ); + } + } ); + } else { + triggerNoticeEnter( overlayNoticeWrapper ); + } + } ); + } + + /** + * 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 ) { + triggerNoticeDismiss( overlayNoticeWrapper.length ? overlayNoticeWrapper : notice ); + } + } ); + } ); + + $( document ).on( 'keydown', function ( event ) { + if ( !overlayNoticeWrapper.length ) { + return; + } + if ( 27 === event.keyCode ) { + triggerNoticeDismiss( overlayNoticeWrapper ); + } + } ); + } ); + + /** + * Show notice and trigger event + * + * @param {jQuery} el The notice element to show + */ + function triggerNoticeEnter( el ) { + // trigger native custom event as jQuery and Vanilla JS both can listen to it. + document.dispatchEvent( new CustomEvent( 'edd_promo_notice_enter', { detail: { notice: el } } ) ); + + el.css( 'display', 'flex' ).hide().fadeIn(); + } + + /** + * Dismiss notice and trigger event + * + * @param {jQuery} el The notice element to dismiss + */ + function triggerNoticeDismiss( el ) { + if ( ! el.is( ':visible' ) ) { + return; + } + + if ( el.is( overlayNoticeWrapper ) ) { + el.fadeOut(); + $( '.edd-extension-manager__key-notice' ).hide(); + } else { + el.slideUp( 400, function () { + $( this ).addClass( 'edd-hidden' ); + } ); + } + + // trigger native custom event as jQuery and Vanilla JS both can listen to it. + document.dispatchEvent( new CustomEvent( 'edd_promo_notice_dismiss', { detail: { notice: el } } ) ); + } +} ); diff --git a/assets/js/admin/components/range-slider/index.js b/assets/js/admin/components/range-slider/index.js new file mode 100644 index 00000000000..d51ec18cdb2 --- /dev/null +++ b/assets/js/admin/components/range-slider/index.js @@ -0,0 +1,36 @@ +/** + * Range Slider Init + * + * @param {string} selector + */ +export const edd_init_range_slider = function( selector ) { + + const min = selector.data( 'min' ) || 0; + const max = selector.data( 'max' ) || 100; + const value = selector.data( 'value' ) || 0; + + const updateSliderInputValue = ( e, ui ) => { + selector.siblings( '.edd-range__input' ).val( ui.value ); + }; + + selector.slider({ + min, + max, + value, + range: 'min', + animate: true, + slide: updateSliderInputValue, + change: updateSliderInputValue, + create: () => { + selector.siblings( '.edd-range__input' ).on( 'input change', function() { + selector.slider( 'value', $( this ).val() ); + }); + } + }); +}; + +jQuery( document ).ready( function( $ ) { + $( '.edd-range__slider' ).each( function() { + edd_init_range_slider( $( this ) ); + }); +} ); 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..6242e319b2c --- /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: 'bottom', + at: 'top-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..4207e3fa811 --- /dev/null +++ b/assets/js/admin/components/vertical-sections/index.js @@ -0,0 +1,81 @@ +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; + } + + // Handle the hash existing on page load. + const hash = window.location.hash, + defaultSectionHash = $( `${ sectionSelector } .section-nav li:first-child a` ).attr( 'href' ); + + if ( hash.length ) { + // Hides the section content. + $( `${ sectionSelector } .section-content` ).hide(); + // When the page loads, make sure a section is selected. + processSectionChange( hash ); + } + + // When a section nav item is clicked. + $( 'body' ).on( 'click', + `${ sectionSelector } .section-nav li a`, + function( e ) { + // Prevent the default browser action when a link is clicked. + e.preventDefault(); + + // Don't do anything if the click is on the actions handle. + if ( e.target.classList.contains( 'edd__handle-actions' ) || e.target.closest( '.edd__handle-actions' ) ) { + return; + } + + let href = $( this ).attr( 'href' ); + + processSectionChange( href ); + + // Do not add the hash to the URL if we are in the download editor. + if ( $( '.edd-download-editor__sections' ).length ) { + return; + } + // Add the current "link" to the page URL + window.history.pushState( 'object or string', '', href ); + } + ); // click() + + $( window ).on( 'hashchange', function() { + processSectionChange( window.location.hash ); + } ); // Back/Forward Navigation. + + function processSectionChange( hash ) { + // If the has is empty or doesn't include edd_, use the default section hash. + if ( hash.length === 0 || ! hash.includes( 'edd_' ) ) { + hash = defaultSectionHash; + } + + // If the selected nav item doesn't exist, use the default section hash. + let selectedNavItem = $( sectionSelector + ' ' + hash + '-nav-item' ); + + if ( ! selectedNavItem.length ) { + hash = defaultSectionHash; + selectedNavItem = $( sectionSelector + ' ' + defaultSectionHash + '-nav-item' ); + } + + let selectedContent = $( sectionSelector + ' ' + hash ), + parents = selectedNavItem.parents( '.edd-vertical-sections' ); + + // Hide all section content. + parents.find( '.section-content' ).hide(); + + // Set the `aria-selected` attribute to false for all section nav items. + parents.find( '.section-title' ).attr( 'aria-selected', 'false' ).removeClass( 'section-title--is-active' ).find( 'a' ).trigger( 'blur' ); + + // Set the `aria-selected` attribute to true for this section nav item. + selectedNavItem.attr( 'aria-selected', 'true' ).addClass( 'section-title--is-active' ).find( 'a' ).trigger( 'focus' ); + + // Find the section content that matches the section nav item and show it. + selectedContent.show(); + + // Maybe re-Chosen + selectedContent.find( 'div.chosen-container' ).css( 'width', '100%' ); + } +} ); diff --git a/assets/js/admin/customers/index.js b/assets/js/admin/customers/index.js new file mode 100644 index 00000000000..9c9e990090b --- /dev/null +++ b/assets/js/admin/customers/index.js @@ -0,0 +1,139 @@ +/** + * 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.parents( '.customer-section' ), + notice = wrapper.find( '.notice-wrap' ), + 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, + }; + + notice.empty(); + button.attr( 'disabled', true ).addClass( 'updating-message' ); + + $.post( ajaxurl, postData, function( response ) { + setTimeout( function() { + if ( true === response.success ) { + window.location.href = response.redirect; + } else { + button.attr( 'disabled', false ).removeClass( 'updating-message' ); + notice.append( '

' + response.message + '

' ); + } + }, 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/generator.js b/assets/js/admin/discounts/generator.js new file mode 100644 index 00000000000..f980c59de4c --- /dev/null +++ b/assets/js/admin/discounts/generator.js @@ -0,0 +1,138 @@ +jQuery( ( $ ) => { + + let successTimeout; + + /** + * When an .edd-toggle is changed in #edd-generator-characters wrapper, if there is only + * one checked, make it readonly. + */ + $( '#edd-generator-characters .edd-toggle' ).on( 'change', function() { + const checked = $( '#edd-generator-characters .edd-toggle input:checked' ), + allInputs = $( '#edd-generator-characters .edd-toggle input' ); + + if ( 1 === checked.length ) { + checked.each( function() { + $( this ).attr( 'readonly', true ); + $( this ).attr( 'disabled', true ); + } ); + } else { + allInputs.each( function() { + $( this ).attr( 'readonly', false ); + $( this ).attr( 'disabled', false ); + } ); + } + } ); + + // When 'Generate' button is clicked, generate a new discount code. + $( '#edd-generate-code' ).on( 'click', function() { + + const hasLetters = $( '#generator-letters' ).is( ':checked' ); + const hasNumbers = $( '#generator-numbers' ).is( ':checked' ); + + if ( ! hasLetters && ! hasNumbers ) { + showPopupError( edd_vars.no_letters_or_numbers ); + return; + } + + if ( successTimeout ) { + clearTimeout( successTimeout ); + $( this ).removeClass( 'updated-message' ); + } + + hidePopupError(); + + $( this ).prop( 'disabled', true ).addClass( 'updating-message' ); + + $.ajax({ + url: ajaxurl, + method: 'POST', + data: { + action: 'edd_admin_generate_discount_code', + 'edd-discount-nonce': $( '[name="edd-discount-nonce"]' ).val(), + prefix: $( '#generator-prefix' ).val(), + limit: $( '#generator-length' ).val(), + letters: hasLetters, + numbers: hasNumbers, + }, + success: ( response ) => { + if ( response.success ) { + $( '#edd-code' ).val( response.data.code ); + $( this ).addClass( 'updated-message' ); + + successTimeout = setTimeout( () => { + $( this ).removeClass( 'updated-message' ); + }, 1000); + } else { + showPopupError( response.data.message ); + } + }, + error: ( error ) => { + showPopupError( error.responseJSON ?? error.responseText ); + }, + complete: () => { + $( this ).prop( 'disabled', false ).removeClass( 'updating-message' ); + } + }); + }); + + $( '.edd-popup-trigger.disabled' ).on( 'mouseover', function() { + $( this ).parent().next( '.edd-code-generator-popup' ).show(); + } ); + + /** + * This prevents hovering over a child element from triggering the mouseout, and hiding the popup prematurely. + */ + $( '.edd-popup-trigger.disabled' ).on( 'mouseout', function() { + return; + } ); + + $( document ) + .on( 'focus', ':input', function() { + if ( isPopupBoundary( $( this ) ) ) { + return; + } + hidePopup( $( '.edd-code-generator-popup' ) ); + }) + .on( 'click touchstart', function(e) { + const target = $( e.target ).closest( '.edd-popup-trigger' ).length ? $( '.edd-popup-trigger' ) : $( e.target ) ; + + if ( target.is( '.edd-popup-trigger' ) ) { + target.parent().next( '.edd-code-generator-popup' ).toggle(); + return; + } + + if ( ! isPopupBoundary( target ) ) { + hidePopup( $( '.edd-code-generator-popup' ) ); + } + }) + .on( 'keyup', function( e ) { + if ( e.keyCode === 27 ) { + hidePopup( $( '.edd-code-generator-popup' ) ); + } + }); +}); + +const hidePopup = () => { + const popup = $( '.edd-code-generator-popup' ); + + popup.hide(); + hidePopupError( popup ); +}; + +const isPopupBoundary = ( target ) => { + return !! target.closest( '.edd-code-generator-popup' ).length; +}; + +const showPopupError = ( message ) => { + const errorElement = $( '
' ).addClass( 'notice notice-error' ).append( $( '

' ).text( message ) ); + errorElement.insertBefore( '#edd-generate-code' ); +} + +const hidePopupError = ( popupElement = false ) => { + if ( ! popupElement ) { + popupElement = $( '.edd-code-generator-popup:not(.hidden)' ); + } + + popupElement.find('.notice').remove(); + popupElement.css( 'margin-top', 0 ); +}; diff --git a/assets/js/admin/discounts/index.js b/assets/js/admin/discounts/index.js new file mode 100644 index 00000000000..8f3233363ad --- /dev/null +++ b/assets/js/admin/discounts/index.js @@ -0,0 +1,27 @@ +/** + * Internal dependencies. + */ +import { jQueryReady } from 'utils/jquery.js'; +import './generator'; + +/** + * DOM ready. + */ +jQueryReady( () => { + const products = $( '#edd_products' ); + const categories = $( '#edd_categories' ); + if ( !products && !categories ) { + return; + } + + /** + * Show/hide conditions based on input value. + */ + products.on( 'change', function () { + $( '#edd-discount-product-conditions' ).toggle( !!products.val().length ); + } ); + + categories.on( 'change', function () { + $( '#edd-discount-category-conditions' ).toggle( !!categories.val().length ); + } ); +} ); 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/copy.js b/assets/js/admin/downloads/copy.js new file mode 100644 index 00000000000..afe106c21ae --- /dev/null +++ b/assets/js/admin/downloads/copy.js @@ -0,0 +1,21 @@ +document.addEventListener( 'click', function ( event ) { + if ( !event.target.classList.contains( 'edd-button__copy' ) ) { + return; + } + const target = event.target.getAttribute( 'data-clipboard-target' ); + const element = document.querySelector( target ); + + element.select(); + element.setSelectionRange( 0, 99999 ); + navigator.clipboard.writeText( element.value ); + + const originalText = event.target.innerText; + + event.target.classList.add( 'updated-message' ); + event.target.innerText = edd_vars.copy_success; + + setTimeout( function() { + event.target.classList.remove( 'updated-message' ); + event.target.innerText = originalText; + }, 2000 ); +} ); diff --git a/assets/js/admin/downloads/editor.js b/assets/js/admin/downloads/editor.js new file mode 100644 index 00000000000..0a061d6008e --- /dev/null +++ b/assets/js/admin/downloads/editor.js @@ -0,0 +1,42 @@ +/** + * Prevents Downloads post type protected meta from being saved. + * + * @url https://github.com/awesomemotive/easy-digital-downloads-pro/issues/725 + * @since 3.2.3 + */ +const preventDownloadsProtectedMetaSave = () => { + + // Add a middleware to WP API Fetch. So it will handle before any api request. + wp.apiFetch.use( + ( options, next ) => { + + // retrieve post type config to get api base. + const downloadConfig = wp.data.select( 'core' ).getEntityConfig( 'postType', 'download' ); + // current request path. + const path = options?.path; + + // if current request path and download api base path is same process the request and remove protected meta. + if ( downloadConfig && path && path.indexOf( downloadConfig.baseURL ) === 0 ) { + + const metas = options?.data?.meta || {}; + + if ( Object.keys( metas ).length ) { + const newMetas = {}; + for ( const [ key, value ] of Object.entries( metas ) ) { + // remove any meta which starts with _edd. + if ( key.indexOf( '_edd' ) !== 0 ) { + newMetas[ key ] = value; + } + } + options.data.meta = newMetas; + } + } + + const result = next( options ); + return result; + } + ) + +} + +wp.domReady( preventDownloadsProtectedMetaSave ); diff --git a/assets/js/admin/downloads/files.js b/assets/js/admin/downloads/files.js new file mode 100644 index 00000000000..5915c26590f --- /dev/null +++ b/assets/js/admin/downloads/files.js @@ -0,0 +1,56 @@ +document.addEventListener( 'DOMContentLoaded', function () { + const productTypeSelect = document.getElementById( '_edd_product_type' ); + if ( ! productTypeSelect ) { + return; + } + + // Listen for the 'change' event + productTypeSelect.addEventListener( 'change', function ( e ) { + const productType = e.target.value; + const productFiles = document.getElementById( 'edd_product_files' ); + const navItem = document.getElementById( 'edd_download_editor__files-nav-item' ); + + // Add the loading class + navItem.classList.add( 'ajax--loading' ); + + // Prepare the AJAX data + const data = new FormData(); + data.append( 'action', 'edd_swap_download_type' ); + data.append( 'product_type', productType ); + data.append( 'post_id', edd_vars.post_id ); + + // Perform the AJAX POST request + fetch( ajaxurl, { + method: 'POST', + body: data, + } ) + .then( response => response.json() ) + .then( response => { + if ( response.success ) { + productFiles.innerHTML = response.data.html; + + // Assuming this function exists globally + if ( typeof EDD_Download_Configuration !== 'undefined' && + typeof EDD_Download_Configuration.initChosen === 'function' ) { + EDD_Download_Configuration.initChosen( productFiles ); + } + + const label = navItem.querySelector( '.label' ); + if ( label ) { + label.textContent = response.data.label; + } + + document.dispatchEvent( new CustomEvent( 'edd_download_type_changed', { + detail: { + productType: productType, + }, + } ) ); + + } + } ) + .catch( error => console.error( 'Error:', error ) ) + .finally( () => { + navItem.classList.remove( 'ajax--loading' ); + } ); + } ); +} ); diff --git a/assets/js/admin/downloads/index.js b/assets/js/admin/downloads/index.js new file mode 100644 index 00000000000..de35be79a8c --- /dev/null +++ b/assets/js/admin/downloads/index.js @@ -0,0 +1,387 @@ +/** + * Internal dependencies. + */ +import { getChosenVars } from 'utils/chosen.js'; +import { edd_attach_tooltips } from 'admin/components/tooltips'; +import './bulk-edit.js'; +import './pricing.js'; +import './files.js'; +import './copy.js'; +import './requirements.js'; +import './move.js'; + +/** + * Download Configuration Metabox + */ +var EDD_Download_Configuration = { + init: function() { + this.add(); + this.move(); + this.remove(); + 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_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' ) ); + EDD_Download_Configuration.triggerRowChange( clone ); + + 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 + EDD_Download_Configuration.initChosen( clone ); + EDD_Download_Configuration.triggerRowChange( clone ); + } ); + }, + + 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++; + } ); + EDD_Download_Configuration.triggerRowChange( $( this ) ); + }, + 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 ); + } ); + } ); + + EDD_Download_Configuration.triggerRowChange( focusElement ); + } ); + }, + + 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( 'blur', '.edd_variable_prices_name', function() { + const key = $( this ).parents( '.edd-section-content__fields--standard' ).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(); + } ); + }, + + initChosen: function ( element ) { + element.find( '.edd-select-chosen' ).each( function () { + const el = $( this ); + el.chosen( getChosenVars( el ) ); + } ); + element.find( '.edd-select-chosen' ).css( 'width', '100%' ); + element.find( '.edd-select-chosen .chosen-search input' ).attr( 'placeholder', edd_vars.search_placeholder ); + }, + + triggerRowChange: function ( el ) { + // trigger native custom event as jQuery and Vanilla JS both can listen to it. + document.dispatchEvent( new CustomEvent( 'edd_repeatable_row_change', { detail: { row: el } } ) ); + } +}; + +jQuery( document ).ready( function( $ ) { + EDD_Download_Configuration.init(); +} ); diff --git a/assets/js/admin/downloads/move.js b/assets/js/admin/downloads/move.js new file mode 100644 index 00000000000..19f77973e6c --- /dev/null +++ b/assets/js/admin/downloads/move.js @@ -0,0 +1,65 @@ +const sections = document.querySelector( '.edd-download-editor__sections' ); +if ( sections ) { + addRowListeners(); +} + +const events = [ 'edd_repeatable_row_change', 'edd_download_type_changed' ]; +events.forEach( function ( event ) { + document.addEventListener( event, function () { + addRowListeners(); + } ); +} ); + +function addRowListeners () { + const dynamicRows = sections.querySelectorAll( '.edd-has-handle-actions' ); + if ( ! dynamicRows.length ) { + return; + } + + updateRowButtons(); + + dynamicRows.forEach( function ( section ) { + section.querySelector( '.edd__handle-actions-order--higher' ).addEventListener( 'click', function () { + const thisSectionTitle = this.closest( '.edd-has-handle-actions' ); + const prevSectionTitle = thisSectionTitle.previousElementSibling; + if ( !prevSectionTitle.classList.contains( 'edd-has-handle-actions' ) ) { + return; + } + this.disabled = true; + prevSectionTitle.insertAdjacentElement( 'beforebegin', thisSectionTitle ); + updateRowButtons(); + } ); + + section.querySelector( '.edd__handle-actions-order--lower' ).addEventListener( 'click', function () { + const thisSectionTitle = this.closest( '.edd-has-handle-actions' ); + const nextSectionTitle = thisSectionTitle.nextElementSibling; + if ( !nextSectionTitle.classList.contains( 'edd-has-handle-actions' ) ) { + return; + } + this.disabled = true; + thisSectionTitle.insertAdjacentElement( 'beforebegin', nextSectionTitle ); + updateRowButtons(); + } ); + } ); +} + +function updateRowButtons () { + const rows = document.querySelectorAll( '.edd-has-handle-actions' ); + rows.forEach( function ( section ) { + section.querySelector( '.edd__handle-actions-order--higher' ).disabled = false; + section.querySelector( '.edd__handle-actions-order--lower' ).disabled = false; + } ); + + const firstSection = rows[ 0 ]; + firstSection.querySelector( '.edd__handle-actions-order--higher' ).disabled = true; + + const lastSection = rows[ rows.length - 1 ]; + lastSection.querySelector( '.edd__handle-actions-order--lower' ).disabled = true; + + // if there is only one section, hide some things + if ( rows.length === 1 ) { + firstSection.querySelector( '.edd__handle-actions-order' ).classList.add( 'edd-hidden' ); + } else { + firstSection.querySelector( '.edd__handle-actions-order' ).classList.remove( 'edd-hidden' ); + } +} diff --git a/assets/js/admin/downloads/pricing.js b/assets/js/admin/downloads/pricing.js new file mode 100644 index 00000000000..80f52a82d77 --- /dev/null +++ b/assets/js/admin/downloads/pricing.js @@ -0,0 +1,228 @@ +const variablePricing = document.getElementById( 'edd_variable_pricing' ); +const variablePricingSection = document.getElementById( 'edd_download_editor__variable-pricing' ); +const variablePriceRowClass = 'edd-section-content__row'; +const variablePriceRowSelector = '.' + variablePriceRowClass; + +// Add an event listener to the variable pricing toggle. +if ( variablePricing ) { + toggleVariablePricing( variablePricing.checked ); + + variablePricing.addEventListener( 'change', function () { + toggleVariablePricing( this.checked ); + } ); +} + +if ( variablePricingSection ) { + addOrderListeners(); + + variablePricingSection.addEventListener( 'click', function ( event ) { + if ( event.target.classList.contains( 'edd-button__add--variation' ) || event.target.closest( '.edd-button__add--variation' ) ) { + cloneSection.call( event.target, event ); + } + if ( event.target.classList.contains( 'edd-section-content__remove' ) ) { + deleteSection.call( event.target, event ); + hideRemoveButton(); + } + + if ( event.target.classList.contains( 'edd-button__edit' ) ) { + const priceRow = event.target.closest( variablePriceRowSelector ); + priceRow.classList.toggle( 'open' ); + priceRow.classList.toggle( 'closed' ); + } + + if ( event.target.classList.contains( 'edd-button__toggle-expand-custom' ) ) { + event.preventDefault(); + const priceRows = variablePricingSection.querySelectorAll( variablePriceRowSelector ); + priceRows.forEach( function ( priceRow ) { + priceRow.classList.add( 'open' ); + priceRow.classList.remove( 'closed' ); + } ); + } + + if ( event.target.classList.contains( 'edd-button__toggle-collapse-custom' ) ) { + event.preventDefault(); + const priceRows = variablePricingSection.querySelectorAll( variablePriceRowSelector ); + priceRows.forEach( function ( priceRow ) { + priceRow.classList.remove( 'open' ); + priceRow.classList.add( 'closed' ); + } ); + } + } ); + + // Ensure only one default price is selected. + variablePricingSection.addEventListener( 'change', function ( event ) { + if ( event.target.name !== '_edd_default_price_id' ) { + return; + } + + if ( event.target.checked ) { + const checkboxes = variablePricingSection.querySelectorAll( 'input[name="_edd_default_price_id"]' ); + checkboxes.forEach( function ( checkbox ) { + if ( checkbox !== event.target ) { + checkbox.checked = false; + } + } ); + } else { + event.target.checked = true; + } + } ); + + // Add an additional listener to the document to handle clicks on the remove button from the modal. + const modal = document.getElementById( 'edd-admin-notice-pricechanges' ); + if ( modal ) { + modal.addEventListener( 'click', function ( event ) { + if ( event.target.classList.contains( 'edd-section-content__remove' ) ) { + deleteSection.call( event.target, event ); + hideRemoveButton(); + } + } ); + } +} + +function addOrderListeners() { + const priceVariations = variablePricingSection.querySelectorAll( variablePriceRowSelector ); + if ( ! priceVariations.length ) { + return; + } + + updateOrderButtons(); + + priceVariations.forEach( function ( section ) { + section.querySelector( '.edd__handle-actions-order--higher' ).addEventListener( 'click', function () { + const thisRow = this.closest( variablePriceRowSelector ); + const previousRow = thisRow.previousElementSibling; + if ( ! previousRow || ! previousRow.classList.contains( variablePriceRowClass ) ) { + return; + } + this.disabled = true; + if ( previousRow ) { + previousRow.insertAdjacentElement( 'beforebegin', thisRow ); + } + updateOrderButtons(); + } ); + + section.querySelector( '.edd__handle-actions-order--lower' ).addEventListener( 'click', function () { + const thisRow = this.closest( variablePriceRowSelector ); + const nextRow = thisRow.nextElementSibling; + if ( ! nextRow || ! nextRow.classList.contains( variablePriceRowClass ) ) { + return; + } + this.disabled = true; + if ( nextRow ) { + thisRow.insertAdjacentElement( 'beforebegin', nextRow ); + } + updateOrderButtons(); + } ); + } ); +} + +function toggleVariablePricing ( enabled ) { + // Get all .edd-bundled-product-row, .edd-repeatable-row-standard-fields; using the document is intentional. + const rows = document.querySelectorAll( '.edd-bundled-product-row, .edd-repeatable-row-standard-fields' ); + rows.forEach( function ( row ) { + if ( enabled ) { + row.classList.add( 'has-variable-pricing' ); + } else { + row.classList.remove( 'has-variable-pricing' ); + } + } ); + hideRemoveButton(); +} + +function cloneSection ( event ) { + event.preventDefault(); + + const button = variablePricingSection.querySelector( '.edd-button__add--variation' ); + button.disabled = true; + + // Get a fresh list of rows. + const updatedVariations = variablePricingSection.querySelectorAll( variablePriceRowSelector ); + const data = new FormData(); + data.append( 'action', 'edd_clone_variation' ); + data.append( 'timestamp', button.dataset.timestamp ); + data.append( 'token', button.dataset.token ); + data.append( 'download_id', edd_vars.post_id ); + + let lastVariationNumber = 0; + updatedVariations.forEach( function ( section ) { + const hiddenInput = section.querySelector( '.edd-section__id' ); + const sectionNumber = parseInt( hiddenInput.value ); + if ( sectionNumber > lastVariationNumber ) { + lastVariationNumber = sectionNumber; + } + } ); + + data.append( 'section', parseInt( lastVariationNumber ) + 1 ); + + fetch( ajaxurl, { + method: 'POST', + body: data, + } ).then( function ( response ) { + return response.json(); + } ).then( function ( data ) { + if ( data.success ) { + const lastVariation = updatedVariations[ updatedVariations.length - 1 ]; + lastVariation.insertAdjacentHTML( 'afterend', data.data ); + + button.disabled = false; + hideRemoveButton(); + updateOrderButtons(); + addOrderListeners(); + } + } ); +} + +function deleteSection ( e ) { + e.preventDefault(); + if ( this.classList.contains( 'edd-promo-notice__trigger' ) ) { + return; + } + + let section = this.closest( variablePriceRowSelector ); + if ( ! section ) { + const dataId = this.getAttribute( 'data-id' ); + if ( dataId ) { + const hiddenInput = variablePricingSection.querySelector( '.edd-section__id[value="' + dataId + '"]' ); + section = hiddenInput.closest( variablePriceRowSelector ); + } + } + + section.remove(); +} + +// Hide the remove button for the first section. +function hideRemoveButton () { + const variationRemoveButtons = variablePricingSection.querySelectorAll( '.edd-section-content__remove' ); + if ( variationRemoveButtons.length === 1 ) { + variationRemoveButtons[ 0 ].classList.add( 'edd-hidden' ); + } else { + variationRemoveButtons.forEach( function ( el ) { + el.classList.remove( 'edd-hidden' ); + } ); + } +} + +function updateOrderButtons() { + const rows = variablePricingSection.querySelectorAll( variablePriceRowSelector ); + if ( ! rows.length ) { + return; + } + rows.forEach( function ( section ) { + section.querySelector( '.edd__handle-actions-order--higher' ).disabled = false; + section.querySelector( '.edd__handle-actions-order--lower' ).disabled = false; + } ); + + const firstVariation = rows[ 0 ]; + firstVariation.querySelector( '.edd__handle-actions-order--higher' ).disabled = true; + + const lastVariation = rows[ rows.length - 1 ]; + lastVariation.querySelector( '.edd__handle-actions-order--lower' ).disabled = true; + + // if there is only one section, hide some things + if ( rows.length === 1 ) { + firstVariation.querySelector( '.edd__handle-actions-order' ).classList.add( 'edd-hidden' ); + } else { + firstVariation.querySelector( '.edd__handle-actions-order' ).classList.remove( 'edd-hidden' ); + } +} + diff --git a/assets/js/admin/downloads/requirements.js b/assets/js/admin/downloads/requirements.js new file mode 100644 index 00000000000..dfcccb2e2b0 --- /dev/null +++ b/assets/js/admin/downloads/requirements.js @@ -0,0 +1,75 @@ +/** + * Toggle visibility of elements based on requirements. To use this, + * add the class `edd-requirement` to the element that should be toggled. This must also have a `data-edd-requirement` attribute + * that is the name of the requirement. Then add a `data-edd-requires-` attribute to the elements that should be toggled based on the requirement. + * The values should be `true` or `false` to indicate if the element should be shown when the requirement is met or not met. + */ + +import { updateSupports } from "./supports"; + +// Look for requirments on initial load. +document.querySelectorAll( '.edd-requirement' ).forEach( function ( element ) { + const requires = element.getAttribute( 'data-edd-requirement' ); + if ( !requires ) { + return; + } + + updateRequirements( requires, element.checked ); +} ); + +// Listen for changes to requirements. +document.addEventListener( 'change', function ( event ) { + if ( !event.target.classList.contains( 'edd-requirement' ) ) { + return; + } + const requires = event.target.getAttribute( 'data-edd-requirement' ); + if ( !requires ) { + return; + } + + updateRequirements( requires, event.target.checked ); +} ); + +/** + * Update the visibility of elements based on requirements. + * + * @since 3.3.6 + * @param {string} requires The requirement to check. + * @param {bool} enabled Whether the requirement is met. + */ +function updateRequirements ( requires, enabled ) { + const elements = document.querySelectorAll( '[data-edd-requires-' + requires + ']' ); + elements.forEach( function ( element ) { + element.classList.remove( 'edd-hidden--required' ); + if ( element.getAttribute( 'data-edd-requires-' + requires ) === 'true' ) { + element.classList.toggle( 'edd-hidden', !enabled ); + } else { + element.classList.toggle( 'edd-hidden', enabled ); + } + if ( element.classList.contains( 'edd-hidden' ) ) { + element.classList.add( 'edd-hidden--required' ); + } + } ); +} + +document.querySelectorAll( '.edd-supports' ).forEach( function ( element ) { + const supports = element.getAttribute( 'data-edd-supported' ); + if ( ! supports ) { + return; + } + + updateSupports( supports, element.value ); +} ); + +// Listen for changes to supported values. +document.addEventListener( 'change', function ( event ) { + if ( ! event.target.classList.contains( 'edd-supports' ) ) { + return; + } + const supports = event.target.getAttribute( 'data-edd-supported' ); + if ( ! supports ) { + return; + } + + updateSupports( supports, event.target.value ); +} ); diff --git a/assets/js/admin/downloads/supports.js b/assets/js/admin/downloads/supports.js new file mode 100644 index 00000000000..5b1c5e5ba15 --- /dev/null +++ b/assets/js/admin/downloads/supports.js @@ -0,0 +1,32 @@ +/** + * Toggle visibility of elements based on supported values. To use this, + * add the class `edd-supports` to the element that should be toggled. This must also have a `data-edd-supported` attribute + * that is the name of the requirement. Then add a `data-edd-supports-` attribute to the elements that should be toggled based on the requirement. + * The values should be comma separated strings to indicate if the element should be shown when the requirement is met or not met. + */ + +/** + * Update the visibility of elements based on supported values. + * + * @since 3.3.6 + * @param {string} supports The requirement to check. + * @param {string} selectedValue The selected value. + */ +export function updateSupports ( supports, supportedValue ) { + const elements = document.querySelectorAll( '[data-edd-supports-' + supports + ']' ); + if ( ! supportedValue.length ) { + supportedValue = 'false'; + } + + elements.forEach( function ( element ) { + if ( element.classList.contains( 'edd-hidden--required' ) ) { + return; + } + const elementSupports = element.getAttribute( 'data-edd-supports-' + supports ); + if ( ! elementSupports.split( ',' ).includes( supportedValue ) ) { + element.classList.add( 'edd-hidden' ); + } else { + element.classList.remove( 'edd-hidden' ); + } + } ); +} diff --git a/assets/js/admin/emails/editor/index.js b/assets/js/admin/emails/editor/index.js new file mode 100644 index 00000000000..481172017a1 --- /dev/null +++ b/assets/js/admin/emails/editor/index.js @@ -0,0 +1,4 @@ +import './recipient.js'; +import './reset.js'; +import './submit.js'; +import './listener.js'; diff --git a/assets/js/admin/emails/editor/listener.js b/assets/js/admin/emails/editor/listener.js new file mode 100644 index 00000000000..f7e3cff4ce9 --- /dev/null +++ b/assets/js/admin/emails/editor/listener.js @@ -0,0 +1,15 @@ +// Listen for any changes to the email editor and set a flag to warn the user if they try to leave the page without saving. +document.addEventListener( 'DOMContentLoaded', function () { + var inputs = document.querySelectorAll( 'input, textarea' ); + for ( var i = 0; i < inputs.length; i++ ) { + inputs[ i ].addEventListener( 'change', function () { + window.onbeforeunload = function () { + return true; + }; + } ); + } + // Remove the warning if the user saves the email. + document.getElementById( 'submit' ).addEventListener( 'click', function () { + window.onbeforeunload = null; + } ); +} ); diff --git a/assets/js/admin/emails/editor/recipient.js b/assets/js/admin/emails/editor/recipient.js new file mode 100644 index 00000000000..a397eb9dd2e --- /dev/null +++ b/assets/js/admin/emails/editor/recipient.js @@ -0,0 +1,21 @@ +; ( function ( document, $ ) { + 'use strict'; + + const recipient = $( '.edd-email__recipient' ); + if ( recipient.length ) { + const custom = $( '.edd-email__recipient--custom' ), + admin = $( '.edd-email__recipient--admin' ); + recipient.on( 'change', function ( e ) { + if ( 'default' === e.target.value ) { + custom.hide(); + admin.show(); + } else if ( 'custom' === e.target.value ) { + custom.show(); + admin.hide(); + } else { + custom.hide(); + admin.hide(); + } + } ); + } +} )( document, jQuery ); diff --git a/assets/js/admin/emails/editor/reset.js b/assets/js/admin/emails/editor/reset.js new file mode 100644 index 00000000000..c2d93fc2811 --- /dev/null +++ b/assets/js/admin/emails/editor/reset.js @@ -0,0 +1,49 @@ +const reset = document.getElementById( 'edd-email-reset' ); +if ( reset ) { + reset.addEventListener( 'click', e => { + e.preventDefault(); + + // disable the button and add updating-message class + reset.classList.remove( 'button-primary' ); + reset.classList.add( 'updating-message' ); + reset.disabled = true; + + // do an ajax call to reset the email settings + const data = { + action: 'edd_reset_email', + nonce: EDDAdminEmails.nonce, + email_id: reset.dataset.email + }; + fetch( EDDAdminEmails.ajaxurl, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: new URLSearchParams( data ) + } ) + .then( response => response.json() ) + .then( response => { + if ( response.success ) { + const editor = tinymce.get( 'edd-email-content' ), + textArea = document.getElementById( 'edd-email-content' ); + if ( editor ) { + editor.setContent( response.data.content ); + } + textArea.value = response.data.content; + document.querySelector( '.edd-promo-notice-dismiss' ).click(); + reset.classList.remove( 'updating-message' ); + reset.classList.add( 'button-primary', 'updated-message' ); + } + } ) + .catch( error => { + console.error( error ); + } ); + } ); + + // Wait for the promo notice to be dismissed to re-enable the button. + document.addEventListener( 'edd_promo_notice_dismiss', e => { + reset.classList.remove( 'updated-message' ); + reset.disabled = false; + } ); +} diff --git a/assets/js/admin/emails/editor/submit.js b/assets/js/admin/emails/editor/submit.js new file mode 100644 index 00000000000..629ebfe00cb --- /dev/null +++ b/assets/js/admin/emails/editor/submit.js @@ -0,0 +1,17 @@ +document.querySelectorAll( '.edd-email-status-badge' ).forEach( function ( el ) { + setTimeout( function () { + if ( ! el.classList.contains( 'edd-hidden' ) ) { + el.classList.add( 'edd-fadeout' ); + } + }, 5000 ); +} ); + +document.getElementById( 'submit' ).addEventListener( 'click', function ( event ) { + document.querySelectorAll( '.edd-email-status-badge' ).forEach( function ( el ) { + if ( !el.classList.contains( 'edd-hidden' ) ) { + el.remove(); + } else { + el.classList.remove( 'edd-hidden' ); + } + } ); +} ); diff --git a/assets/js/admin/emails/list-table/index.js b/assets/js/admin/emails/list-table/index.js new file mode 100644 index 00000000000..a38e135abaf --- /dev/null +++ b/assets/js/admin/emails/list-table/index.js @@ -0,0 +1,3 @@ +import './list-table'; +import './new-email'; +import './status'; diff --git a/assets/js/admin/emails/list-table/list-table.js b/assets/js/admin/emails/list-table/list-table.js new file mode 100644 index 00000000000..fadbb3f7749 --- /dev/null +++ b/assets/js/admin/emails/list-table/list-table.js @@ -0,0 +1,121 @@ +/** + * Email filtering. + */ +const statusFilter = document.getElementById( 'edd-email-status-filter' ); +const recipientFilter = document.getElementById( 'edd-email-recipient-filter' ); +const senderFilter = document.getElementById( 'edd-email-sender-filter' ); +const contextFilter = document.getElementById( 'edd-email-context-filter' ); +const clearFilters = document.getElementById( 'edd-email-clear-filters' ); +const noItemsFound = document.getElementById( 'no-items' ); + +if ( statusFilter ) { + statusFilter.addEventListener( 'change', updateEmailFilters ); +} +if ( recipientFilter ) { + recipientFilter.addEventListener( 'change', updateEmailFilters ); +} +if ( senderFilter ) { + senderFilter.addEventListener( 'change', updateEmailFilters ); +} +if ( contextFilter ) { + contextFilter.addEventListener( 'change', updateEmailFilters ); +} + +/** + * Updates the table to only show emails that match the filters. + * + * @since 2.7 + * + * @param e + */ +function updateEmailFilters ( e ) { + const tableRows = document.querySelectorAll( 'table.email_templates tbody tr:not(#no-items)' ); + + if ( !tableRows || !( statusFilter || recipientFilter || senderFilter ) ) { + return; + } + + const chosenStatus = statusFilter ? statusFilter.value : '', + chosenRecipient = recipientFilter ? recipientFilter.value : '', + chosenSender = senderFilter ? senderFilter.value : '', + chosenContext = contextFilter ? contextFilter.value : ''; + + clearFilters.style.display = 'none'; + if ( chosenStatus || chosenRecipient || chosenSender || chosenContext ) { + clearFilters.style.display = 'inline-block'; + } + + tableRows.forEach( tableRow => { + // Always show the row by default, because it's easier to start that way. + tableRow.style = ''; + tableRow.classList.remove( 'edd-hidden', 'alternate' ); + + if ( chosenStatus && chosenStatus !== tableRow.getAttribute( 'data-status' ) ) { + hideRow( tableRow ); + } + + if ( chosenRecipient && chosenRecipient !== tableRow.getAttribute( 'data-recipient' ) ) { + hideRow( tableRow ); + } + + if ( chosenSender && chosenSender !== tableRow.getAttribute( 'data-sender' ) ) { + hideRow( tableRow ); + } + + if ( chosenContext && chosenContext !== tableRow.getAttribute( 'data-context' ) ) { + hideRow( tableRow ); + } + } ); + + // If there are no rows with data-type="item" visible, then toggle the "no items found" message. + let visibleRows = document.querySelectorAll( '[data-type="item"]:not(.edd-hidden)' ); + updateRowClass( visibleRows ); + if ( visibleRows.length === 0 ) { + noItemsFound.style = 'table-row'; + } else { + noItemsFound.style.display = 'none'; + } +} + +/** + * Hides a table row and any associated extra content row. + * + * @param {HTMLElement} tableRow - The table row element to hide. + */ +function hideRow( tableRow ) { + tableRow.style.display = 'none'; + tableRow.classList.add( 'edd-hidden' ); +} + +/** + * Updates the row class of elements. + * + * @param {Array} elements - The elements to update the row class for. + */ +function updateRowClass( elements ) { + elements.forEach( ( element, index ) => { + if ( index % 2 === 0 ) { + element.classList.add( 'alternate' ); + } + } ); +} + +if ( clearFilters ) { + clearFilters.addEventListener( 'click', e => { + e.preventDefault(); + if ( statusFilter ) { + statusFilter.value = ''; + } + if ( recipientFilter ) { + recipientFilter.value = ''; + } + if ( senderFilter ) { + senderFilter.value = ''; + } + + if ( contextFilter ) { + contextFilter.value = ''; + } + updateEmailFilters(); + } ); +} diff --git a/assets/js/admin/emails/list-table/new-email.js b/assets/js/admin/emails/list-table/new-email.js new file mode 100644 index 00000000000..b711052dec6 --- /dev/null +++ b/assets/js/admin/emails/list-table/new-email.js @@ -0,0 +1,43 @@ +const newEmail = document.getElementById( 'edd-emails__add' ); +const overlay = document.querySelector( '.edd-emails__add-new__overlay' ); +if ( newEmail ) { + newEmail.addEventListener( 'click', e => { + e.preventDefault(); + + // if the overlay has a display:none, remove the style + if ( overlay.style.display === 'none' ) { + overlay.removeAttribute( 'style' ); + } else { + overlay.style.display = 'none'; + } + } ); + + document.addEventListener( 'click', e => { + if ( newEmail === e.target || overlay.style.display === 'none' ) { + return; + } + if ( overlay.style.display !== 'none' ) { + setTimeout( function () { + if ( !e.target.closest( '.edd-emails__add-new' ) && !e.target.closest( '.edd-emails__add-new__overlay' ) ) { + overlay.style.display = 'none'; + } + }, 100 ); + } + } ); +} + +const addNewEmail = document.querySelectorAll( 'button.edd-emails__add-new' ); +if ( addNewEmail ) { + addNewEmail.forEach( addNewEmail => { + addNewEmail.addEventListener( 'click', e => { + e.preventDefault(); + if ( !addNewEmail.classList.contains( 'edd-promo-notice__trigger' ) ) { + window.location.href = EDDAdminEmails.link + '&email=' + addNewEmail.getAttribute( 'data-value' ); + } else { + setTimeout( function () { + overlay.style.display = 'none'; + }, 5000 ); + } + } ); + } ); +} diff --git a/assets/js/admin/emails/list-table/status.js b/assets/js/admin/emails/list-table/status.js new file mode 100644 index 00000000000..467a26b9f24 --- /dev/null +++ b/assets/js/admin/emails/list-table/status.js @@ -0,0 +1,61 @@ +/* global EDDAdminEmails */ + +; ( function ( document, $ ) { + 'use strict'; + + $( '.edd-email-manager__action' ).on( 'click', function ( e ) { + e.preventDefault(); + + const $btn = $( this ), + action = $btn.attr( 'data-action' ); + + let removeClass = '', + addClass = '', + replaceAction = '', + replaceStatus = ''; + + if ( $btn.attr( 'disabled' ) ) { + return; + } + + switch ( action ) { + case 'enable': + addClass = 'edd-button-toggle--active'; + replaceAction = 'disable'; + replaceStatus = 'inactive'; + break; + + case 'disable': + removeClass = 'edd-button-toggle--active'; + replaceAction = 'enable'; + replaceStatus = 'active'; + break; + + default: + return; + } + + $btn.attr( 'disabled', true ).addClass( 'edd-updating' ); + + const data = { + action: 'edd_update_email_status', + nonce: EDDAdminEmails.nonce, + email_id: $btn.attr( 'data-id' ), + status: $btn.attr( 'data-status' ), + button: action, + }; + + $.post( EDDAdminEmails.ajaxurl, data ) + .done( function ( res ) { + if ( EDDAdminEmails.debug ) { + console.log( res ); + } + $btn.attr( 'disabled', false ).removeClass( 'edd-updating' ); + if ( res.success ) { + $btn.removeClass( removeClass ).addClass( addClass ); + $btn.attr( 'data-action', replaceAction ); + $btn.attr( 'data-status', replaceStatus ); + } + } ); + } ); +} )( document, jQuery ); diff --git a/assets/js/admin/flyout/index.js b/assets/js/admin/flyout/index.js new file mode 100644 index 00000000000..cf35cf2431a --- /dev/null +++ b/assets/js/admin/flyout/index.js @@ -0,0 +1,98 @@ +// Flyout Menu. +import {domReady} from 'utils/dom'; + +var EDD_Flyout = { + flyoutMenu: null, + wpfooter: null, + overlap: null, + init: function() { + // Flyout Menu Elements. + this.flyoutMenu = document.getElementById('edd-flyout'); + + if (!this.flyoutMenu) { + return; + } + + var head = document.getElementById('edd-flyout-button'), + edd = head.querySelector('img'), + items = document.getElementById('edd-flyout-items'), + menu = { + state: 'inactive', + srcInactive: edd.getAttribute('src'), + srcActive: edd.dataset.active, + }; + + // Click on the menu head icon. + head.addEventListener('click', (e) => { + e.preventDefault(); + + if (menu.state === 'active') { + this.flyoutMenu.classList.remove('opened'); + edd.setAttribute('src', menu.srcInactive); + menu.state = 'inactive'; + items.classList.remove('active'); + } else { + this.flyoutMenu.classList.add('opened'); + edd.setAttribute('src', menu.srcActive); + menu.state = 'active'; + items.classList.add('active'); + } + }); + + // Page elements and other values. + this.wpfooter = document.getElementById('wpfooter'); + + if (!this.wpfooter) { + return; + } + + // If we run into pages that need to have this disabled, we can add them here. + //this.overlap = document.querySelectorAll('#target-selector'); + this.overlap = {}; + + // Hide menu if scrolled down to the bottom of the page. + window.addEventListener('resize', this.handleScroll.bind(this)); + + window.addEventListener('scroll', () => this.debounce(this.handleScroll.bind(this), 50)); + + window.addEventListener('load', this.handleScroll.bind(this)); + + document.addEventListener( 'edd_promo_notice_enter', () => this.flyoutMenu.classList.add('out') ); + document.addEventListener( 'edd_promo_notice_dismiss', () => setTimeout( this.flyoutMenu.classList.remove('out'), 500 ) ); + }, + handleScroll: function() { + if ( this.overlap.length < 1 ) { + return; + } + + var wpfooterTop = this.wpfooter.offsetTop, + wpfooterBottom = wpfooterTop + this.wpfooter.offsetHeight, + overlapBottom = this.overlap.length > 0 ? this.overlap[0].offsetTop + this.overlap[0].offsetHeight + 85 : 0, + viewTop = window.scrollY, + viewBottom = viewTop + window.innerHeight; + + if (wpfooterBottom <= viewBottom && wpfooterTop >= viewTop && overlapBottom > viewBottom) { + this.flyoutMenu.classList.add('out'); + } else { + this.flyoutMenu.classList.remove('out'); + } + }, + debounce: function(func, wait, immediate) { + var timeout; + return function () { + var context = this, + args = arguments; + var later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + } + } +} +domReady(function() { + EDD_Flyout.init(); +}) diff --git a/assets/js/admin/index.js b/assets/js/admin/index.js new file mode 100755 index 00000000000..4ec92c0b23d --- /dev/null +++ b/assets/js/admin/index.js @@ -0,0 +1,18 @@ +/** + * 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/range-slider'; +import './components/navigation'; +// 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/notifications/index.js b/assets/js/admin/notifications/index.js new file mode 100644 index 00000000000..88c44f0e962 --- /dev/null +++ b/assets/js/admin/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/onboarding/index.js b/assets/js/admin/onboarding/index.js new file mode 100644 index 00000000000..ff54bd32e61 --- /dev/null +++ b/assets/js/admin/onboarding/index.js @@ -0,0 +1,770 @@ +/** + * 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 ) { + stripe_connect_account.removeClass( 'loading' ) + stripe_connect_actions.removeClass( 'loading' ); + + // 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); + } + + let data = { + action: 'edd_onboarding_save_email', + content: purchase_receipt_content, + email_logo: $( '#email_logo' ).val(), + from_name: $( '#from_name' ).val(), + from_email: $( '#from_email' ).val(), + nonce: EDD_Onboarding.vars.nonce, + }; + + return $.ajax( { + type: 'POST', + url: ajaxurl, + data: data, + 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() { + if ( $( this ).data( 'plugin-name' ) && $( this ).data( 'action' ).length > 0 ) { + 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' ), + auto_register: $( '#auto-register' ).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() { + if ( $( this ).data( 'plugin-name' ) && $( this ).data( 'action' ).length > 0 ) { + 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..b2e2a18f0c1 --- /dev/null +++ b/assets/js/admin/orders/order-details/customer.js @@ -0,0 +1,70 @@ +/* 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-order-customer__actions button' ).on( 'click', function( e ) { + e.preventDefault(); + + var new_customer = $( this ).hasClass( 'edd-payment-new-customer' ), + cancel = $( this ).hasClass( 'edd-payment-new-customer-cancel' ); + $( this ).addClass( 'active' ).siblings().removeClass( 'active' ); + + 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..99e8f9e3517 --- /dev/null +++ b/assets/js/admin/orders/order-details/receipt.js @@ -0,0 +1,30 @@ +/* global $, ajaxurl */ + +/** + * Internal dependencies + */ +import { jQueryReady } from 'utils/jquery.js'; + +jQueryReady( () => { + + const sendEmailButton = $( '#edd-resend-receipt' ); + // If the button is disabled, do nothing. + if ( ! sendEmailButton.attr( 'href' ) ) { + return; + } + const emailSelectSelector = '.edd-order-resend-receipt-email'; + 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/stripe/index.js b/assets/js/admin/stripe/index.js new file mode 100644 index 00000000000..7d30f74c105 --- /dev/null +++ b/assets/js/admin/stripe/index.js @@ -0,0 +1,125 @@ +/* global $, edd_stripe_admin */ + +/** + * Internal dependencies. + */ +// import './../../../css/src/admin.scss'; +import './settings/index.js'; +import './settings/payment-methods.js'; + +let testModeCheckbox; +let testModeToggleNotice; + +$( document ).ready( function() { + testModeCheckbox = document.getElementById( 'edd_settings[test_mode]' ); + if ( testModeCheckbox ) { + testModeToggleNotice = document.getElementById( 'edd_settings[stripe_connect_test_mode_toggle_notice]' ); + EDD_Stripe_Connect_Scripts.init(); + } + + // Toggle API keys. + $( '.edds-api-key-toggle button' ).on( 'click', function( event ) { + event.preventDefault(); + + $( '.edds-api-key-toggle, .edds-api-key-row' ) + .toggleClass( 'edd-hidden' ); + } ); + + const elementsModeToggle = $( '.stripe-elements-mode select' ); + if ( elementsModeToggle ) { + + // Listen to the elements mode toggle. + elementsModeToggle.on( 'change', function() { + $( '.card-elements-feature' ).toggleClass( 'edd-hidden' ); + $( '.payment-elements-feature' ).toggleClass( 'edd-hidden' ); + } ); + } + + /** + * Handle showing/hiding the Statement Descriptor Prefix field based on the toggle. + */ + const statementDescriptorSummaryToggle = document.getElementById( 'edd_settings[stripe_include_purchase_summary_in_statement_descriptor]' ); + if ( statementDescriptorSummaryToggle ) { + statementDescriptorSummaryToggle.addEventListener( 'change', function() { + $( '.statement-descriptor-prefix' ).toggleClass( 'edd-hidden' ); + } ); + } +} ); + +const EDD_Stripe_Connect_Scripts = { + + init() { + this.listeners(); + }, + + listeners() { + const self = this; + + testModeCheckbox.addEventListener( 'change', function() { + // Don't run these events if Stripe is not enabled. + if ( ! edd_stripe_admin.stripe_enabled ) { + return; + } + + if ( this.checked ) { + if ( 'false' === edd_stripe_admin.test_key_exists ) { + self.showNotice( testModeToggleNotice, 'warning' ); + self.addHiddenMarker(); + } else { + self.hideNotice( testModeToggleNotice ); + const hiddenMarker = document.getElementById( 'edd-test-mode-toggled' ); + if ( hiddenMarker ) { + hiddenMarker.parentNode.removeChild( hiddenMarker ); + } + } + } + + if ( ! this.checked ) { + if ( 'false' === edd_stripe_admin.live_key_exists ) { + self.showNotice( testModeToggleNotice, 'warning' ); + self.addHiddenMarker(); + } else { + self.hideNotice( testModeToggleNotice ); + const hiddenMarker = document.getElementById( 'edd-test-mode-toggled' ); + if ( hiddenMarker ) { + hiddenMarker.parentNode.removeChild( hiddenMarker ); + } + } + } + } ); + }, + + addHiddenMarker() { + const submit = document.getElementById( 'submit' ); + + if ( ! submit ) { + return; + } + + submit.parentNode.insertAdjacentHTML( 'beforeend', '' ); + }, + + showNotice( element = false, type = 'error' ) { + if ( ! element ) { + return; + } + + if ( typeof element !== 'object' ) { + return; + } + + element.className = 'notice inline notice-' + type; + }, + + hideNotice( element = false ) { + if ( ! element ) { + return; + } + + if ( typeof element !== 'object' ) { + return; + } + + element.className = 'edd-hidden'; + }, +}; diff --git a/assets/js/admin/stripe/notices.js b/assets/js/admin/stripe/notices.js new file mode 100644 index 00000000000..80653d4dab7 --- /dev/null +++ b/assets/js/admin/stripe/notices.js @@ -0,0 +1,36 @@ +/* global wp, jQuery */ + +/** + * Handle dismissing admin notices. + */ +jQuery( () => { + /** + * Loops through each admin notice on the page for processing. + * + * @param {HTMLElement} noticeEl Notice element. + */ + jQuery( '.edds-admin-notice' ).each( function() { + const notice = $( this ); + const id = notice.data( 'id' ); + const nonce = notice.data( 'nonce' ); + + /** + * Listens for a click event on the dismiss button, and dismisses the notice. + * + * @param {Event} e Click event. + * @return {jQuery.Deferred} Deferred object. + */ + notice.on( 'click', '.notice-dismiss', ( e ) => { + e.preventDefault(); + e.stopPropagation(); + + return wp.ajax.post( + 'edds_admin_notices_dismiss_ajax', + { + id, + nonce, + } + ); + } ); + } ); +} ); \ No newline at end of file diff --git a/assets/js/admin/stripe/settings/index.js b/assets/js/admin/stripe/settings/index.js new file mode 100644 index 00000000000..611ab4ab78e --- /dev/null +++ b/assets/js/admin/stripe/settings/index.js @@ -0,0 +1,5 @@ +/** + * Internal dependencies + */ +import './requirements.js'; +import './stripe-connect.js'; diff --git a/assets/js/admin/stripe/settings/payment-methods.js b/assets/js/admin/stripe/settings/payment-methods.js new file mode 100644 index 00000000000..518663fa2c5 --- /dev/null +++ b/assets/js/admin/stripe/settings/payment-methods.js @@ -0,0 +1,29 @@ +const toggles = document.querySelectorAll( '.edd-stripe-payment-method-toggle' ); + +toggles.forEach( function ( button ) { + button.addEventListener( 'click', function ( e ) { + e.preventDefault(); + toggleElement( this.dataset.toggle ); + toggles.forEach( function ( button ) { + button.classList.remove( 'active' ); + } ); + this.classList.add( 'active' ); + } ); +} ); + +function toggleElement( value ) { + var elements = document.querySelectorAll( '.edd-stripe-payment-method' ); + for ( var i = 0; i < elements.length; i++ ) { + elements[ i ].style.display = 'none'; + } + + var selectedElements; + if ( ! value ) { + selectedElements = elements; + } else { + selectedElements = document.querySelectorAll( '.edd-stripe-payment-method--' + value ); + } + for ( var i = 0; i < selectedElements.length; i++ ) { + selectedElements[ i ].style.display = 'block'; + } +} diff --git a/assets/js/admin/stripe/settings/requirements.js b/assets/js/admin/stripe/settings/requirements.js new file mode 100644 index 00000000000..dc8e3a52399 --- /dev/null +++ b/assets/js/admin/stripe/settings/requirements.js @@ -0,0 +1,40 @@ +/** + * Internal dependencies + */ +import { domReady } from 'utils'; + +/** + * Hides "Save Changes" button if showing the special settings placeholder. + */ +domReady( () => { + const containerEl = document.querySelector( '.edds-requirements-not-met' ); + + if ( ! containerEl ) { + return; + } + + // Hide "Save Changes" button. + document.querySelector( '.edd-settings-wrap .submit' ).style.display = 'none'; +} ); + +/** + * Moves "Payment Gateways" notice under Stripe. + * Disables/unchecks the checkbox. + */ +domReady( () => { + const noticeEl = document.getElementById( 'edds-payment-gateways-stripe-unmet-requirements' ); + + if ( ! noticeEl ) { + return; + } + + const stripeLabel = document.querySelector( 'label[for="edd_settings[gateways][stripe]"]' ); + stripeLabel.parentNode.insertBefore( noticeEl, stripeLabel.nextSibling ); + + const stripeCheck = document.getElementById( 'edd_settings[gateways][stripe]' ); + stripeCheck.disabled = true; + stripeCheck.checked = false; + + noticeEl.insertBefore( stripeCheck, noticeEl.querySelector( 'p' ) ); + noticeEl.insertBefore( stripeLabel, noticeEl.querySelector( 'p' ) ); +} ); diff --git a/assets/js/admin/stripe/settings/stripe-connect.js b/assets/js/admin/stripe/settings/stripe-connect.js new file mode 100644 index 00000000000..fda898a8e5b --- /dev/null +++ b/assets/js/admin/stripe/settings/stripe-connect.js @@ -0,0 +1,54 @@ +/** + * Internal dependencies + */ +import { domReady, apiRequest } from 'utils'; + +// Wait for DOM. +domReady( () => { + const containerEl = document.getElementById( 'edds-stripe-connect-account' ); + const actionsEl = document.getElementById( 'edds-stripe-disconnect-reconnect' ); + + + if ( ! containerEl ) { + return; + } + + /* + * Do not make a request, if we are inside Onboarding Wizard. + * Onboarding Wizard will make it's own call. + */ + if ( containerEl.hasAttribute('data-onboarding-wizard') ) { + return; + } + + return apiRequest( 'edds_stripe_connect_account_info', { + ...containerEl.dataset, + } ) + .done( ( response ) => { + containerEl.classList.remove( 'loading' ); + containerEl.innerHTML = response.message; + containerEl.classList.add( `notice-${ response.status }` ); + + actionsEl.classList.remove( 'loading' ); + if ( response.actions ) { + actionsEl.innerHTML = response.actions; + } + + const statement_descriptor_target = document.getElementById( 'edd_settings[stripe_statement_descriptor]' ), + statement_descriptor_prefix_target = document.getElementById( 'edd_settings[stripe_statement_descriptor_prefix]' ); + + statement_descriptor_target.classList.remove( 'edd-text-loading' ); + statement_descriptor_prefix_target.classList.remove( 'edd-text-loading' ); + if ( response.account ) { + const statement_descriptor = response.account.settings.payments.statement_descriptor || '', + statement_descriptor_prefix = response.account.settings.card_payments.statement_descriptor_prefix || ''; + + statement_descriptor_target.value = statement_descriptor || ''; + statement_descriptor_prefix_target.value = statement_descriptor_prefix || ''; + } + } ) + .fail( ( error ) => { + containerEl.innerHTML = error.message; + containerEl.classList.add( 'notice-error' ); + } ); +} ); 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. + $notice_args = wp_parse_args( + $args, + array( + 'id' => '', + 'message' => '', + 'class' => false, + 'is_dismissible' => true, + ) + ); + + // Prevent a notice from being added more than once. + if ( ! empty( $notice_args['id'] ) && array_key_exists( $notice_args['id'], $this->notices ) ) { + return; + } + + $default_class = 'updated'; + + // One message as string. + if ( is_string( $notice_args['message'] ) ) { + $message = '

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

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

' . implode( '

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

'; + + // Messages as objects. + } elseif ( is_wp_error( $notice_args['message'] ) ) { + $default_class = 'is-error'; + $errors = $notice_args['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( + 'notice', + 'edd-notice', + $default_class, + ); + if ( ! empty( $notice_args['class'] ) ) { + $classes = array_merge( $classes, explode( ' ', $notice_args['class'] ) ); + } + + // Add dismissible class. + if ( ! empty( $notice_args['is_dismissible'] ) ) { + array_push( $classes, 'is-dismissible' ); + } + + // Assemble the message. + $message = '
' . $message . '
'; + $message = str_replace( "'", "\'", $message ); + + // Add notice to notices array. + $this->notices[ $notice_args['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(); + $this->add_stripe_notice(); + $this->add_paypal_sync_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 unneeded 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', + 'message' => sprintf( + /* translators: %s: URL to the settings page */ + __( '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' ), + sprintf( + wp_kses( + /* translators: %s: Dismiss notice URL */ + __( 'If you have already done this, or it does not apply to your site, you may permanently dismiss this notice.', 'easy-digital-downloads' ), + array( + 'a' => array( + 'href' => array(), + ), + ) + ), + $dismiss_notice_url + ), + ), + ), + ); + } + + // 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( + /* translators: %s: Uploads directory */ + sprintf( __( 'The .htaccess file is missing from: %s', 'easy-digital-downloads' ), '' . $upload_directory . '' ), + /* translators: %s: Uploads directory */ + sprintf( __( 'First, please re-save 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 . '' ), + /* translators: %s: Notice Dismissal URL */ + sprintf( __( 'If you have already done this, or it does not apply to your site, you may permanently %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', + '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. + $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( ! in_array( $page, array( 'edd-settings', 'edd-emails' ), true ) ) { + 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_SPECIAL_CHARS ) ) { + 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' ); + } + } + + if ( 'paypal_commerce' === filter_input( INPUT_GET, 'section', FILTER_SANITIZE_SPECIAL_CHARS ) ) { + if ( edd_is_test_mode() && ! \EDD\Gateways\PayPal\has_rest_api_connection( 'sandbox' ) ) { + $this->add_notice( + array( + 'message' => sprintf( + /* translators: %s: Sandbox doc link */ + __( 'Connecting to PayPal in Test Mode requires the use of Sandbox Credentials. If you need help finding this, you can view our documentation on using PayPal Sandbox.', 'easy-digital-downloads' ), + 'https://easydigitaldownloads.com/docs/paypal-setup/#sandbox' + ), + 'class' => 'notice-warning', + 'is_dismissible' => false, + ) + ); + } + } + } + + /** + * 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, + ) + ); + } + + /** + * Adds a notice if the Stripe Pro gateway is outdated. + * This is due to a name change in the gateway. + * + * @since 3.2.1 + * @return void + */ + private function add_stripe_notice() { + if ( ! defined( 'EDD_STRIPE_VERSION' ) || ( defined( 'EDD_STRIPE_VERSION' ) && version_compare( EDD_STRIPE_VERSION, '2.8.4', '>=' ) ) ) { + return; + } + $this->add_notice( + array( + 'id' => 'edd-stripe-outdated', + 'class' => 'notice-warning', + 'message' => sprintf( + /* translators: 1: opening link tag, 2: opening link tag, 3: closing link tag. */ + __( 'You are running an outdated version of the Easy Digital Downloads — Stripe Pro Payment Gateway. You may need to log into %1$syour account%3$s to download the latest version and %2$smanually upgrade%3$s it.', 'easy-digital-downloads' ), + '', + '', + '' + ), + 'is_dismissible' => false, + ) + ); + } + + /** + * Adds a notice if PayPal webhooks need to synced. + * + * @since 3.2.1 + * @return void + */ + private function add_paypal_sync_notice() { + if ( ! get_option( 'edd_paypal_webhook_sync_failed' ) ) { + return; + } + $url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + 'section' => 'paypal_commerce', + ) + ); + + $this->add_notice( + array( + 'id' => 'edd-paypal-webhook-sync', + 'class' => 'updated', + 'message' => sprintf( + /* translators: 1: Opening anchor tag; %2$s: Closing anchor tag. */ + __( 'New webhooks have been registered for PayPal Commerce, but we were unable to update them automatically. Please %1$ssync your webhooks manually%2$s.', 'easy-digital-downloads' ), + '', + '' + ), + 'is_dismissible' => false, + ) + ); + } + + /** + * Notices about actions that the user has taken + * + * @since 3.0 + * + * @param string $notice The notice key. + */ + 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; + case 'discount_archived': + $this->add_notice( + array( + 'id' => 'edd-discount-archived', + 'message' => __( 'Discount code archived.', 'easy-digital-downloads' ), + ) + ); + break; + case 'discount_archived_failed': + $this->add_notice( + array( + 'id' => 'edd-discount-archived-fail', + 'message' => __( 'There was a problem archiving 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-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; + + case 'stripe_webhooks_created': + $this->add_notice( + array( + 'id' => 'edd-webhooks-created', + 'message' => __( 'Webhooks have been created for the Stripe gateway.', 'easy-digital-downloads' ), + ) + ); + break; + + case 'stripe_webhooks_error': + $this->add_notice( + array( + 'id' => 'edd-webhooks-error', + 'message' => __( 'There was an error creating webhooks for the Stripe gateway.', 'easy-digital-downloads' ), + 'class' => 'error', + ) + ); + break; + + case 'stripe_webhooks_error_ssl': + $this->add_notice( + array( + 'id' => 'edd-webhooks-error', + 'message' => __( 'Stripe webhooks could not be created because your site is not using HTTPS.', '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-not-sent', + 'message' => __( 'The purchase receipt could not be resent.', 'easy-digital-downloads' ), + 'class' => 'error', + ) + ); + break; + case 'email_possibly_not_sent': + $this->add_notice( + array( + 'id' => 'edd-order-receipt-maybe-not-sent', + 'message' => __( 'The purchase receipt may not have been sent.', 'easy-digital-downloads' ), + 'class' => 'notice-warning', + ) + ); + break; + case 'payment-note-deleted': + $this->add_notice( + array( + 'id' => 'edd-note-deleted', + 'message' => __( 'The order note has been deleted.', 'easy-digital-downloads' ), + ) + ); + break; + case 'order_recalculated': + $this->add_notice( + array( + 'id' => 'edd-order-recalculated', + 'message' => __( 'The order values have been recalculated.', 'easy-digital-downloads' ), + ) + ); + break; + case 'order_not_recalculated': + $this->add_notice( + array( + 'id' => 'edd-order-not-recalculated', + 'message' => __( 'The order values could not be recalculated.', 'easy-digital-downloads' ), + 'class' => 'error', + ) + ); + 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' ), + '', + '' + ), + ) + ); + } + + if ( current_user_can( 'edit_users' ) && 'capabilities_reset' === $notice ) { + $this->add_notice( + array( + 'id' => 'edd-capabilities-reset', + 'message' => __( 'The user capabilities for Easy Digital Downloads have been reset.', '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 Message to escape. + * + * @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..f94e6f73dd7 --- /dev/null +++ b/includes/admin/class-list-table.php @@ -0,0 +1,249 @@ + 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'; + $status = $this->get_status(); + + if ( ! empty( $orderby ) ) { + echo ''; + } + + if ( ! empty( $order ) ) { + echo ''; + } + + if ( ! empty( $status ) ) { + echo ''; + } + + ?> + + + + '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 The current 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 ) ) || ( 'active' !== $item_status ) ) { + 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 The current 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 The current 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( + '', + esc_attr( 'customer' ), + esc_attr( $item['id'] ), + /* translators: %s: the customer name */ + esc_html( sprintf( _x( 'Select %s', 'Noun: The customer name', '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 If we're rendering the top or bottom nav. + * @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 The current 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 ) ) || ( 'active' !== $item_status ) ) { + 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 The current 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 The current 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 The current 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( + '', + esc_attr( 'customer' ), + esc_attr( $item['id'] ), + /* translators: %s: the customer email address */ + esc_html( sprintf( _x( 'Select %s', 'Noun: The customer email address', 'easy-digital-downloads' ), $item['email'] ) ), + esc_attr( $title ), + $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, true ) ) { + 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 Which table nav we're rendering, top or bottom. + * @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 The current 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 ) ) || ( 'active' !== $item_status ) ) { + switch ( $item_status ) { + case 'pending': + $value = __( 'Pending', 'easy-digital-downloads' ); + break; + case 'disabled': + $value = __( 'Disabled', 'easy-digital-downloads' ); + break; + case 'inactive': + $value = __( 'Inactive', '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 The current 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( + '', + 'customer', + esc_attr( $item['id'] ), + /* translators: %s: the customer name or email address */ + esc_html( sprintf( _x( 'Select %s', 'Noun: The customer name or Email Address', '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, + 'status' => $customer->status, + ); + } + } + + 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 Which part of the table nav we're rendering, top or bottom. + * @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['status'] = $customer_info['status']; + + $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() ) { + + if ( ! is_admin() || ! current_user_can( edd_get_edit_customers_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 ) ); + } + + if ( ! current_user_can( edd_get_edit_customers_role() ) ) { + wp_die( __( 'You do not have permission to edit this customer.', 'easy-digital-downloads' ) ); + } + + $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 ) ); + } + + if ( ! current_user_can( edd_get_edit_customers_role() ) ) { + wp_die( __( 'You do not have permission to edit this customer.', 'easy-digital-downloads' ) ); + } + + $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(); + /* translators: 1: email address, 2: username */ + $customer_note = sprintf( __( 'Email address %1$s set as primary 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' => '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() ) { + + if ( ! is_admin() || ! current_user_can( edd_get_edit_customers_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( __( 'Nonce verification failed.', '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() ) { + if ( ! is_admin() || ! current_user_can( edd_get_edit_customers_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( __( 'Nonce verification failed.', '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 ) ); + } + + if ( ! is_admin() || ! current_user_can( edd_get_edit_customers_role() ) ) { + wp_die( __( 'You do not have permission to edit this customer.', 'easy-digital-downloads' ) ); + } + + $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..b6ad425d57d --- /dev/null +++ b/includes/admin/customers/customer-functions.php @@ -0,0 +1,131 @@ +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 ); ?> +
+ $tab_name ) { + $tabs[ $tab_id ] = array( + 'name' => $tab_name, + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'page_type' => urlencode( $tab_id ), + ) + ), + ); + } + + $navigation = new EDD\Admin\Menu\SecondaryNavigation( + $tabs, + 'edd-customers', + array( + 'active_tab' => $active_tab, + 'show_search' => true, + ) + ); + $navigation->render(); +} + +/** + * Retrieve the customer pages. + * + * Used only by the primary tab navigation for customers. + * + * @since 3.0 + * + * @return array + */ +function edd_get_customer_pages() { + static $pages = null; + + // Filter. + if ( null === $pages ) { + $pages = (array) apply_filters( + 'edd_get_customer_pages', + array( + 'customers' => 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 EDD_Customer $customer Customer object. + */ +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( $customer ); + $views = edd_customer_views( $customer ); + + // 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' ); + + // If we're not looking at a specific customer, we can just load the main list table based on the tab. + if ( empty( filter_input( INPUT_GET, 'id', FILTER_VALIDATE_INT ) ) ) { + // Tabs. + $active_tab = ! empty( $_GET['page_type'] ) + ? sanitize_key( $_GET['page_type'] ) + : 'customers'; + + edd_customers_list( $active_tab ); + return; + } + + // If we have customer ID, determine which view to load. + $default_views = edd_customer_views(); + $requested_view = isset( $_GET['view'] ) + ? sanitize_key( $_GET['view'] ) + : 'overview'; + + // If the requested view is valid, render it. + if ( array_key_exists( $requested_view, $default_views ) && is_callable( $default_views[ $requested_view ] ) ) { + // Single customer view. + edd_render_customer_view( $requested_view, $default_views ); + } else { + edd_render_customer_view( 'overview', $default_views ); + } +} + +/** + * Register the views for customer management + * + * @since 2.3 + * @param \EDD_Customer $customer Customer object. + * @return array Array of views and their callbacks + */ +function edd_customer_views( $customer = null ) { + return array_merge( + array( + 'overview' => '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', + ), + apply_filters( 'edd_customer_views', array(), $customer ) + ); +} + +/** + * Register the tabs for customer management + * + * @since 2.3 + * @param \EDD_Customer $customer Customer object. + * @return array Array of tabs for the customer + */ +function edd_customer_tabs( $customer = null ) { + $tabs = array_merge( + 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' ), + ), + ), + apply_filters( 'edd_customer_tabs', array(), $customer ) + ); + + // Ensure that the delete tab is always at the bottom. + $tabs['delete'] = array( + 'dashicon' => 'dashicons-trash', + 'title' => _x( 'Delete', 'Delete Customer tab title', 'easy-digital-downloads' ), + ); + + return $tabs; +} + +/** + * 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 __DIR__ . '/class-customer-table.php'; + $list_table_class = 'EDD_Customer_Reports_Table'; + break; + case 'emails': + include_once __DIR__ . '/class-customer-email-addresses-table.php'; + $list_table_class = 'EDD_Customer_Email_Addresses_Table'; + break; + case 'physical': + include_once __DIR__ . '/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(); + edd_customers_page_primary_nav( $active_tab ); + ?> + +
+

+
+ + + + + views(); + /* translators: the active screen, eg "Search Customers" or "Search Customer Email Addresses" */ + $customers_table->search_box( sprintf( _x( 'Search %s', 'Noun: Customers or Customer Email Addresses placeholder for a search box', 'easy-digital-downloads' ), $name ), 'edd-customers' ); + $customers_table->display(); + ?> + + + + + + + +
+ + + +
+

+ + +

+ +
+ + + +
+ + + +
+ +
+ + + +
+ 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 = ''; + $agreement_date_format = 'H:i:s'; + + 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 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 ) : ?> + + + + + + + +
+
+ +
+
+ + + +
+ +
+ + + +
+

+

+ $timestamp ) { + $agreement_timestamps[ $key ] = date_i18n( + get_option( 'date_format' ) . ' ' . $agreement_date_format, + $timestamp + ) . ' ' . edd_get_timezone_abbr(); + } + + $tooltip = new EDD\HTML\TimelineTooltip( + array( + 'title' => __( 'Previous Agreement Dates', 'easy-digital-downloads' ), + 'items' => $agreement_timestamps, + ) + ); + $tooltip->output(); + } + } elseif ( ! empty( $last_payment_date && $show_terms ) ) { + echo esc_html( + edd_date_i18n( + $last_payment_date, + get_option( 'date_format' ) . ' ' . $agreement_date_format + ) . ' ' . edd_get_timezone_abbr() + ); + + esc_html_e( ' — Agreed to Terms', 'easy-digital-downloads' ); + $tooltip = new EDD\HTML\Tooltip( + array( + 'title' => __( 'Estimated Terms Agreement Date', 'easy-digital-downloads' ), + 'content' => __( '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.', 'easy-digital-downloads' ), + ) + ); + $tooltip->output(); + } else { + esc_html_e( 'No terms agreement found.', 'easy-digital-downloads' ); + } + ?> +

+ +

+ $timestamp ) { + $privacy_timestamps[ $key ] = date_i18n( + get_option( 'date_format' ) . ' ' . $agreement_date_format, + $timestamp + ) . ' ' . edd_get_timezone_abbr(); + } + + $tooltip = new EDD\HTML\TimelineTooltip( + array( + 'title' => __( 'Previous Agreement Dates', 'easy-digital-downloads' ), + 'items' => $privacy_timestamps, + ) + ); + $tooltip->output(); + } + } elseif ( ! empty( $last_payment_date ) && $show_privacy ) { + + echo esc_html( + edd_date_i18n( + $last_payment_date, + get_option( 'date_format' ) . ' ' . $agreement_date_format + ) . ' ' . edd_get_timezone_abbr() + ); + + esc_html_e( ' — Agreed to Privacy Policy', 'easy-digital-downloads' ); + $tooltip = new EDD\HTML\Tooltip( + array( + 'title' => __( 'Estimated Privacy Policy Date', 'easy-digital-downloads' ), + 'content' => __( '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.', 'easy-digital-downloads' ), + ) + ); + $tooltip->output(); + } else { + esc_html_e( 'No privacy policy agreement found.', 'easy-digital-downloads' ); + } + ?> +

+
+ + + +
+ + + +

+
+ + + + + + + + + + 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, $order ) ); ?>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', + ) + ); + ?> +
+ +

+ __( 'This customer can use any of the emails listed here when making new purchases.', 'easy-digital-downloads' ), + ) + ); + $tooltip->output(); + ?> +

+ +
+ + + + + + + + + + $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..b4472bee6ac --- /dev/null +++ b/includes/admin/dashboard-widgets.php @@ -0,0 +1,327 @@ +%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 ) { + if ( 1 === $num_posts->publish ) { + $text = sprintf( + /* translators: %s: Download label singular */ + __( '1 %s', 'easy-digital-downloads' ), + edd_get_label_singular() + ); + } else { + $text = sprintf( + /* translators: 1: Number of downloads, 2: Download label plural */ + __( '%1$d %2$s', 'easy-digital-downloads' ), + number_format_i18n( $num_posts->publish ), + edd_get_label_plural() + ); + } + + if ( current_user_can( 'edit_products' ) ) { + $text = '' . $text . ''; + } else { + $text = '' . $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..9757c6397ac 100755 --- a/includes/admin/discounts/add-discount.php +++ b/includes/admin/discounts/add-discount.php @@ -1,98 +1,271 @@ -

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

-
- - - -

-
- - - -

-
- - - -

-
- - - -

-
- - - -

-
- - - -

-
- - - -

-
-

- - - -

-
\ No newline at end of file +
+

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

+
+ + +
+ + +
+ +

+
+ + + + + + + +

+
+ + + + html->product_dropdown( + array( + 'name' => 'product_reqs[]', + 'id' => 'edd_products', + 'selected' => array(), + 'multiple' => true, + 'chosen' => true, + /* translators: %s: Downloads plural label */ + 'placeholder' => sprintf( esc_html_x( 'Select %s', 'Noun: The plural label for the download post type as a placeholder for a dropdown', 'easy-digital-downloads' ), esc_html( edd_get_label_plural() ) ), + 'variations' => true, + ) + ); // WPCS: XSS ok. + ?> + + +

+
+ + + + html->product_dropdown( + array( + 'name' => 'excluded_products[]', + 'id' => 'excluded_products', + 'selected' => array(), + 'multiple' => true, + 'chosen' => true, + /* translators: %s: Downloads plural label */ + 'placeholder' => sprintf( esc_html_x( 'Select %s', 'Noun: The plural label for the download post type as a placeholder for a dropdown', 'easy-digital-downloads' ), esc_html( edd_get_label_plural() ) ), + ) + ); // WPCS: XSS ok. + ?> + +

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

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

+
+ + + +

+
+ + + +

+
+ + + 'once_per_customer', + 'label' => __( 'Prevent customers from using this discount more than once.', 'easy-digital-downloads' ), + ) + ); + $toggle->output(); + ?> +
+ + + +

+ + + + + +

+
+
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..a045f4b9421 --- /dev/null +++ b/includes/admin/discounts/class-discount-codes-table.php @@ -0,0 +1,626 @@ + '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' ), + 'status' => __( 'Status', '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 ), + 'status' => array( 'status', 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(); + $row_actions = array(); + $status = $this->get_status(); + + // Bail if current user cannot manage discounts. + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + return; + } + + // 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' ) . ''; + } + + // Archive. + if ( 'archived' !== strtolower( $discount->status ) ) { + $row_actions['archive'] = '' . __( 'Archive', '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 ); + + $discount_title = '' . stripslashes( $discount->name ) . ''; + + /** + * 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( + '', + 'discount', + absint( $discount->id ), + /* translators: %s: Discount code title (name) */ + esc_html( sprintf( _x( 'Select %s', 'Noun: The discount code title (name)', '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 . ''; + } + + /** + * Returns the discount status column. + * + * @since 3.2.0 + * + * @param EDD_Discount $discount Discount object. + * @return string Discount type HTML. + */ + public function column_status( $discount ) { + $icon = ''; + $status = $discount->status; + $label = edd_get_discount_status_label( $discount->id ); + switch ( $status ) { + case 'active': + $status = 'success'; + break; + case 'inactive': + break; + case 'expired': + $icon = 'backup'; + $status = 'warning'; + break; + } + + if ( ( ! $this->get_status() || 'active' === $this->get_status() ) && ! $discount->is_started( false ) ) { + $icon = 'clock'; + $status = 'info'; + $label = __( 'Scheduled', 'easy-digital-downloads' ); + } + + if ( $discount->is_maxed_out( false ) ) { + $icon = 'yes'; + $status = 'inactive'; + $label = __( '100% Claimed', 'easy-digital-downloads' ); + } + + $status_badge = new EDD\Utils\StatusBadge( + array( + 'status' => $status, + 'label' => $label, + 'icon' => $icon, + 'class' => "edd-admin-discount-status-badge--{$discount->status}", + ) + ); + + return $status_badge->get(); + } + + /** + * Returns the discount use count column. + * + * @since 3.2.0 + * + * @param EDD_Discount $discount Discount object. + * @return string Discount use count HTML. + */ + public function column_use_count( $discount ) { + $max_uses = $discount->max_uses > 0 ? $discount->max_uses : '∞'; + $uses = sprintf( '%d / %s', $discount->use_count, $max_uses ); + + if ( $discount->max_uses > 0 ) { + $progress_bar = new EDD\Utils\ProgressBar( + array( + 'size' => 'small', + 'current_count' => $discount->use_count, + 'total_count' => $discount->max_uses, + 'show_percentage' => true, + 'show_current' => true, + 'show_total' => true, + ) + ); + + return $progress_bar->get(); + } + + return $uses; + } + + /** + * 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() { + $bulk_actions = array( + 'activate' => __( 'Activate', 'easy-digital-downloads' ), + 'deactivate' => __( 'Deactivate', 'easy-digital-downloads' ), + 'archive' => __( 'Archive', 'easy-digital-downloads' ), + 'delete' => __( 'Delete', 'easy-digital-downloads' ), + ); + + $status_actions = array( + 'active' => 'activate', + 'inactive' => 'deactivate', + 'archived' => 'archive', + ); + + $status = $this->get_status(); + if ( array_key_exists( $status, $status_actions ) ) { + unset( $bulk_actions[ $status_actions[ $status ] ] ); + } + + return $bulk_actions; + } + + /** + * 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 'activate': + edd_update_discount_status( $id, 'active' ); + break; + + case 'archive': + edd_update_discount_status( $id, 'archived' ); + 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(); + + // Ensure that 'Archived' is the last status in the status links. + if ( isset( $this->counts['archived'] ) ) { + $archived_counts = $this->counts['archived']; + unset( $this->counts['archived'] ); + $this->counts['archived'] = $archived_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. + $args = array( + 'search' => $this->get_search(), + ); + + // Searches shouldn't have a status check. + if ( empty( $args['search'] ) ) { + if ( empty( $this->get_status() ) ) { + $args['status__not_in'] = array( 'archived' ); + } else { + $args['status'] = $this->get_status(); + } + } + + $this->args = $this->parse_pagination_args( $args ); + + // 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 Which side of the table we're rendering, top or bottom. + * @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' ) . '

' . + /* translators: %s: Documentation URL */ + '

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

' . + '

' . sprintf( + /* translators: %s: Upgrade URL */ + __( '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..f713be57e62 --- a/includes/admin/discounts/discount-actions.php +++ b/includes/admin/discounts/discount-actions.php @@ -2,114 +2,461 @@ /** * 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; // @codeCoverageIgnore /** - * 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 code exists already. + 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( + 'status' => 'active', + 'once_per_customer' => 0, + 'min_charge_amount' => 0, + 'max_uses' => 0, + ); + + $data = array_filter( $data ); + $data = wp_parse_args( $data, $to_add ); + + 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': + case 'edd-action': + case 'edd-discount-nonce': + case 'edd-redirect': + break; + + case 'product_reqs': + case 'categories': + $to_add[ $column ] = $value; + break; + + case 'amount': + case 'min_charge_amount': + $to_add[ $column ] = edd_sanitize_amount( $value ); + break; + + case 'once_per_customer': + $to_add['once_per_customer'] = (int) (bool) $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'] ) ? preg_filter( '/\d|\d_\d/', '$0', (array) $data['product_reqs'] ) : ''; // only accepts patterns like 123 or 123_4. + $to_add['excluded_products'] = isset( $data['excluded_products'] ) ? wp_parse_id_list( $data['excluded_products'] ) : ''; + $to_add['categories'] = isset( $data['categories'] ) ? wp_parse_id_list( $data['categories'] ) : array(); + $to_add['term_condition'] = isset( $data['term_condition'] ) ? $data['term_condition'] : ''; + + $to_add = array_filter( $to_add ); + + // Strip out data that should not be sent to the query methods. + $to_strip = array( + 'discount-id', + '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'; + + 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. * - * @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' ) ); + } + + // Set the update defaults. + $to_update = array( + 'min_charge_amount' => 0, + 'max_uses' => 0, + 'once_per_customer' => 0, + ); + + $data = array_filter( $data ); + $data = wp_parse_args( $data, $to_update ); + + 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': + case 'edd-redirect': + case 'edd-action': + case 'edd-discount-nonce': + case '_wp_http_referer': + break; + + case 'amount': + case 'min_charge_amount': + $to_update[ $column ] = edd_sanitize_amount( $value ); + break; + + case 'once_per_customer': + $to_update['once_per_customer'] = (int) (bool) $value; + break; + + default: + $to_update[ $column ] = is_array( $value ) + ? array_map( 'sanitize_text_field', $value ) + : 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'] ) ? preg_filter( '/\d|\d_\d/', '$0', (array) $data['product_reqs'] ) : ''; // only accepts patterns like 123 or 123_4. + $to_update['excluded_products'] = isset( $data['excluded_products'] ) ? wp_parse_id_list( $data['excluded_products'] ) : ''; + $to_update['categories'] = ! empty( $data['categories'] ) ? wp_parse_id_list( $data['categories'] ) : array(); + $to_update['term_condition'] = isset( $data['term_condition'] ) ? $data['term_condition'] : ''; + + // Strip out known non-columns. + $to_strip = array( + + // Legacy. + 'discount-id', + + // 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'; + + 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 and deletes the + * discount code * - * Listens for when a discount delete button is clicked. - * - * @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( __( 'Nonce verification failed.', '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'; + 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( __( 'Nonce verification failed.', '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'; + + 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( __( 'Nonce verification failed.', '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'; + + 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' ); + +/** + * Archive Discount + * + * Sets a discount status to archived + * + * @since 3.2.0 + * @param array $data Discount code data. + * + * @uses edd_update_discount_status() + */ +function edd_archive_discount( $data = array() ) { + // Bail if no nonce or nonce fails. + if ( ! isset( $data['_wpnonce'] ) || ! wp_verify_nonce( $data['_wpnonce'], 'edd_discount_nonce' ) ) { + wp_die( __( 'Nonce verification failed.', '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'] ); + $archived = edd_update_discount_status( $discount_id, 'archived' ); + $arg = ! empty( $archived ) + ? 'discount_archived' + : 'discount_archived_failed'; + + 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_archive_discount', 'edd_archive_discount' ); diff --git a/includes/admin/discounts/discount-codes.php b/includes/admin/discounts/discount-codes.php index 7a180c39c02..cf1acadc2e3 100755 --- a/includes/admin/discounts/discount-codes.php +++ b/includes/admin/discounts/discount-codes.php @@ -2,130 +2,80 @@ /** * 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'] ) ) { + if ( ! current_user_can( 'edit_shop_discounts' ) ) { + wp_die( __( 'You do not have permission to edit discounts.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + 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'] ) ) { + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + wp_die( __( 'You do not have permission to manage discounts.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + 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() { + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + wp_die( __( 'You do not have permission to manage discounts.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + 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, 'categories', true ); + $term_condition = edd_get_adjustment_meta( $discount->id, 'term_condition', true ); + $term_condition = $term_condition ?: ''; + require_once 'views/categories.php'; + ?> + + 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, + /* translators: %s: Downloads plural label */ + 'placeholder' => sprintf( _x( 'Select %s', 'Noun: The plural label for the download post type as a placeholder for a dropdown', 'easy-digital-downloads' ), edd_get_label_plural() ), + 'variations' => true, + ) + ); + ?> +
> +

+ +

+

+
+ +

+
+

+
+ + + html->product_dropdown( + array( + 'name' => 'excluded_products[]', + 'id' => 'excluded_products', + 'selected' => $excluded_products, + 'multiple' => true, + 'chosen' => true, + /* translators: %s: Downloads plural label */ + 'placeholder' => sprintf( _x( 'Select %s', 'Noun: The plural label for the download post type as a placeholder for a dropdown', 'easy-digital-downloads' ), edd_get_label_plural() ), + ) + ); + ?> +

+ +

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

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

+
+ + + +

+
+ + + +

+
+ + + 'once_per_customer', + 'current' => $single_use, + 'label' => __( 'Prevent customers from using this discount more than once.', 'easy-digital-downloads' ), + ) + ); + $toggle->output(); + ?> +
+ + + +

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

+ + + + 'edd-discounts', + 'edd-action' => 'edit_discount', + 'discount' => absint( $discount->id ), + ) + ) + ); + ?> + + + +

+
+
diff --git a/includes/admin/discounts/views/categories.php b/includes/admin/discounts/views/categories.php new file mode 100644 index 00000000000..3ca761e62b8 --- /dev/null +++ b/includes/admin/discounts/views/categories.php @@ -0,0 +1,44 @@ + + + + + + + 'categories[]', + 'id' => 'edd-categories', + 'selected' => $categories ?: array(), // phpcs:ignore Universal.Operators.DisallowShortTernary.Found + 'multiple' => true, + 'chosen' => true, + 'show_option_all' => false, + 'show_option_none' => false, + 'number' => 30, + ) + ); + echo $dropdown->get(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> +
+

+ +

+
+

+ +

+ + diff --git a/includes/admin/downloads/contextual-help.php b/includes/admin/downloads/contextual-help.php new file mode 100755 index 00000000000..656a52b145c --- /dev/null +++ b/includes/admin/downloads/contextual-help.php @@ -0,0 +1,150 @@ +id ) { + 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' ) . '

' . + /* translators: %s: Documentation URL */ + '

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

' . + '

' . sprintf( + /* translators: %s: Upgrade URL */ + __( 'Need more from your Easy Digital Downloads store? Upgrade Now!', 'easy-digital-downloads' ), + $upgrade_url + ) . '

' + ); + } + + $screen->add_help_tab( + array( + 'id' => 'edd-download-configuration', + /* translators: %s: Download singular label */ + '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', + /* translators: %s: Download singular label */ + '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', + /* translators: %s: Download singular label */ + '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', + /* translators: %s: Download singular label */ + 'title' => sprintf( __( '%s Instructions', 'easy-digital-downloads' ), edd_get_label_singular() ), + /* translators: %s: Download singular label */ + '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( + /* translators: 1: Shortcodes Codex URL, 2: EDD Documentation URL */ + __( '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..0d58031b2fa 100755 --- a/includes/admin/downloads/dashboard-columns.php +++ b/includes/admin/downloads/dashboard-columns.php @@ -2,272 +2,369 @@ /** * 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( + /* translators: %s: Download Category taxonomy name */ + 'placeholder' => sprintf( _x( 'Search %s', 'plural: Example: "Search Download Categories"', 'easy-digital-downloads' ), $category_labels['name'] ), + 'search-type' => 'download_category', + /* translators: %s: Download Category taxonomy name */ + '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 ) && '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( 'add_meta_boxes', 'edd_add_download_meta_box' ); + +add_action( 'save_post', 'edd_download_meta_box_save', 10, 2 ); + /** - * Download Meta Box Save + * Save post meta when the save_post action is called * - * Save data from meta box. + * As a note, this entire function is reliant on the fact that the edd_download_metabox_fields() function + * orders the _variable_pricing field before the edd_variable_prices field. If this is changed, this function + * will fail to detect that variable pricing is enabled since the option hasn't been updated to enable it yet. * - * @access private - * @since 1.0 - * @return void + * This shouldn't be an issue, but since there is a filter on these fields, it is possible for a developer to adjust the order + * of the fields, causing problems, but we should fix that in a future refactor of this. + * + * @since 3.2 + * @param int $post_id Download (Post) ID. + * @global WP_Post $post All the data of the the current post. + * @return void */ -function edd_download_meta_box_save( $post_id) { - global $post; - - // verify nonce - if ( ! isset( $_POST['edd_download_meta_box_nonce'] ) || ! wp_verify_nonce( $_POST['edd_download_meta_box_nonce'], basename( __FILE__ ) ) ) - return $post_id; - - // check autosave - if ( ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) || ( defined( 'DOING_AJAX') && DOING_AJAX ) || isset( $_REQUEST['bulk_edit'] ) ) return $post_id; - - //don't save if only a revision - if ( isset( $post->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_fields_save( $post_id, $post ) { + if ( ! current_user_can( 'edit_product', $post_id ) ) { + 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] ); + // 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 ] ) ) { + $use_value = ( ! empty( $_POST[ $field ] ) && is_numeric( $_POST[ $field ] ) ) || ( 0 === (int) $_POST[ $field ] ); + + $new_default_price_id = $use_value ? + intval( $_POST[ $field ] ) : + 1; } else { - $new = $_POST[ $field ]; + $new_default_price_id = 1; } - - $new = apply_filters( 'edd_metabox_save_' . $field, $new ); + update_post_meta( $post_id, $field, $new_default_price_id ); + continue; + } + + // No value stored when product type is "default" ("0") for backwards compatibility. + if ( '_edd_product_type' === $field && empty( $_POST[ $field ] ) ) { + delete_post_meta( $post_id, '_edd_product_type' ); + continue; + } + + // Skip saving bundled products if not set so that a previous value is not lost. + if ( '_edd_bundled_products' === $field && ! isset( $_POST[ $field ] ) ) { + continue; + } + + $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 { delete_post_meta( $post_id, $field ); } } + + 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 ); } -add_action( 'save_post', 'edd_download_meta_box_save' ); -/** Download Configuration *****************************************************************/ /** - * Download Metabox + * Sanitize bundled products on save + * + * Ensures a user doesn't try and include a product's ID in the products bundled with that product * - * Extensions (as well as the core plugin) can add items to the main download - * configuration metabox via the `edd_meta_box_fields` action. + * @since 1.6 * - * @access private - * @since 1.0 - * @return void + * @param array $products Array of product IDs. + * @return array */ -function edd_render_download_meta_box() { - global $post, $edd_options; - - do_action( 'edd_meta_box_fields', $post->ID ); - wp_nonce_field( basename( __FILE__ ), 'edd_download_meta_box_nonce' ); +function edd_sanitize_bundled_products_save( $products = array() ) { + + $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 { + $product_id = $value; + } + + if ( in_array( intval( $product_id ), array( 0, get_the_ID() ), true ) ) { + 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' ); /** - * Price section. + * Sanitize bundled products conditions on save * - * If variable pricing is not enabled, simply output a single input box. + * @since 3.1 * - * 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` + * @param array $bundled_products_conditions Array of 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. * - * @see edd_render_price_row() + * 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. * - * @access private - * @since 1.0 - * @return void + * @since 1.2.2 + * @param array $updated_meta Array of all the meta values. + * @return array $new New meta value with empty keys removed */ -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_metabox_save_check_blank_rows( $updated_meta ) { + foreach ( $updated_meta as $key => $value ) { + if ( empty( $value['name'] ) && empty( $value['amount'] ) && empty( $value['file'] ) ) { + unset( $updated_meta[ $key ] ); + } + } -
> - - -
- - - - - - - - - - - $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' ) ); - ?> - - - - - - - - - - - - - -
- -
-
-
- null, - 'amount' => null - ); - - $args = wp_parse_args( $args, $defaults ); - extract( $args, EXTR_SKIP ); -?> - - - - - - - - - - - - - - - - × - -get_type(); + } + if ( 'bundle' !== $type ) { + return; + } + include 'views/metabox-bundled-products.php'; } -add_action( 'edd_render_price_row', 'edd_render_price_row', 10, 3 ); - +add_action( 'edd_meta_box_files_fields', 'edd_render_products_field', 10, 2 ); /** * File Downloads section. * * Outputs a table of all current files. Extensions can add column heads to the table - * via the `edd_download_file_table_head` hook, and actual columns via + * via the `edd_download_file_table_head` hook, and actual columns via * `edd_download_file_table_row` * - * @see edd_render_file_row() - * - * @access private - * @since 1.0 - * @return void + * @since 1.0 + * @see edd_render_file_row() + * @param int $post_id Download (Post) ID. + * @param string $type The download type (used in ajax requests since 3.2.0). + * @return void */ -function edd_render_files_field( $post_id ) { - $files = edd_get_download_files( $post_id ); - $variable_pricing = edd_has_variable_prices( $post_id ); - $variable_display = $variable_pricing ? '' : 'display:none;'; -?> -
+function edd_render_files_field( $post_id = 0, $type = '' ) { + if ( is_numeric( $post_id ) && ! current_user_can( 'edit_product', $post_id ) ) { + return; + } -

- -

+ if ( is_null( $post_id ) && ! current_user_can( 'edit_products' ) ) { + return; + } - - -
- - - - - - - - - - - - $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' ) ); - ?> - - - - - - - - - - - - -
- -
-
-
- null, - 'file' => null, - 'condition' => null +function edd_render_file_row( $key, $args, $post_id, $index ) { + + $args = wp_parse_args( + $args, + array( + 'name' => null, + 'file' => null, + 'condition' => null, + 'attachment_id' => null, + 'thumbnail_size' => null, + ) ); - $args = wp_parse_args( $args, $defaults ); - extract( $args, EXTR_SKIP ); + $prices = edd_get_variable_prices( $post_id ); + $has_variable_prices = edd_has_variable_prices( $post_id ); + $file_row_classes = array( 'edd-repeatable-row-standard-fields' ); + if ( $has_variable_prices ) { + $file_row_classes[] = 'has-variable-pricing'; + } + ?> + +
+ + ' . esc_html( $key ) . '' + ); + + printf( + '', + esc_attr( $key ), + esc_attr( $index ) + ); + ?> + + +
+ + + + +
+ + + + + + +
+
- $prices = edd_get_variable_prices( $post_id ); +
+
+ +
+ + + 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', + ) + ); + ?> +
+
- $variable_pricing = edd_has_variable_prices( $post_id ); - $variable_display = $variable_pricing ? '' : ' style="display:none;"'; -?> - - - - - - +
+ +
+ 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']; + } + } + + $select = new \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, + ) + ); + $select->output(); + ?> +
+
+ + + +
+ -

- -

- -

- -

-post_type ) ) { + return $strings; + } + $downloads_object = get_post_type_object( 'download' ); + $labels = $downloads_object->labels; + + /* translators: %s: Download singular label, in lowercase form. */ + $strings['insertIntoPost'] = sprintf( __( 'Insert into %s', 'easy-digital-downloads' ), strtolower( $labels->singular_name ) ); + + return $strings; +} +add_filter( 'media_view_strings', 'edd_download_media_strings', 10, 1 ); /** - * Don't save blank rows. + * Refund Window * - * 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. + * The refund window is the maximum number of days each + * can be downloaded by the buyer * - * @access private - * @since 1.2.2 - * @return array $new New meta value with empty keys removed + * @since 3.0 + * @param int $post_id Download (Post) ID. + * @return void */ -function edd_metabox_save_check_blank_rows( $new ) { - foreach ( $new as $key => $value ) { - if ( $value['name'] == '' ) - unset( $new[ $key ] ); +function edd_render_refund_row( $post_id, $download = null ) { + + // Bail if user cannot manage shop settings. + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; } - return $new; -} -add_filter( 'edd_metabox_save_edd_variable_prices', 'edd_metabox_save_check_blank_rows' ); -add_filter( 'edd_metabox_save_edd_download_files', 'edd_metabox_save_check_blank_rows' ); + $types = edd_get_refundability_types(); + $global_ability = edd_get_option( 'refundability', 'refundable' ); + $global_window = edd_get_option( 'refund_window', 30 ); + $edd_refund_window = $download ? $download->get_refund_window() : false; + ?> + +
+ +
+ 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: %s: Default refund status */ + esc_html_x( 'Default (%s)', 'Download refund status', 'easy-digital-downloads' ), + $types[ $global_ability ], + ), + ), + $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, + ) + ); + ?> +
+
-/** Product Notes *****************************************************************/ +
+ +
+ + +
+

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

+
+ ID ); +function edd_render_download_limit_row( $post_id, $download = null ) { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + // Get the download limit directly from the post meta so that the global is not involved. + $edd_download_limit = get_post_meta( $post_id, '_edd_download_limit', true ); + ?> +
get_files() ) ) { + echo 'data-edd-supports-product-type="false"'; + } + ?> + > + +
+ +
+

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

+
+ - -

- + +
+
+ '_edd_download_tax_exclusive', + 'id' => '_edd_download_tax_exclusive', + 'current' => $exclusive, + 'class' => 'edd-form-group__input', + 'label' => __( 'This product is non-taxable', 'easy-digital-downloads' ), + ) + ); + $checkbox->output(); + ?> +
+

+ +

+
+ quantities_disabled() : false; + ?> + +
+
+ '_edd_quantities_disabled', + 'id' => '_edd_quantities_disabled', + 'current' => $disabled, + 'class' => 'edd-form-group__input', + 'label' => __( 'Disable quantity input for this product', 'easy-digital-downloads' ), + ) + ); + $checkbox->output(); + ?> +
+

+ +

+ + 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 ( ! current_user_can( 'edit_product', $post_id ) ) { + return; + } + + 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']; +function edd_render_disable_button( $download_id ) { + if ( ! current_user_can( 'edit_product', $download_id ) ) { + return; + } + + $shortcode = sprintf( + '[purchase_link id="%1$d"]', + absint( $download_id ) + ); + $buy_button = sprintf( '', $download_id ); + $add_to_cart_link = add_query_arg( + array( + 'edd_action' => 'add_to_cart', + 'download_id' => (int) $download_id, + ), + edd_get_checkout_uri() + ); + $supports_buy_now = edd_shop_supports_buy_now(); + $content = __( 'By default, the buy button will be displayed at the bottom of the download. Disable the default buy button and use the EDD Buy Button block to place the button where you prefer.', 'easy-digital-downloads' ); + if ( $supports_buy_now ) { + $content .= '

'; + $content .= __( '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' ); + } + ?> + +
+

+ __( 'Buy Buttons', 'easy-digital-downloads' ), + 'content' => $content, + ) + ); + $tooltip->output(); + ?> +

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ '_edd_hide_purchase_link', + 'id' => '_edd_hide_purchase_link', + 'current' => $is_disabled, + 'class' => 'edd-form-group__input', + 'label' => __( 'Hide default purchase button.', 'easy-digital-downloads' ), + ); + if ( ! $is_disabled ) { + $post_content = get_post_field( 'post_content', $download_id ); + if ( ! empty( $post_content ) && false !== strpos( $post_content, $buy_button ) ) { + $checkbox_args['tooltip'] = array( + 'title' => __( 'Buy Button Block Detected', 'easy-digital-downloads' ), + 'content' => __( 'The Buy Button block is in the post content, so we recommend disabling the default purchase button.', 'easy-digital-downloads' ), + 'dashicon' => 'dashicons-warning', + ); + } } - echo '
'; - - echo ''; - - echo ''; - - echo ''; - echo ''; - } // endforeach - do_action('edd_purchase_log_meta_box'); - } else { - echo ''; - echo ''; - echo ''; - } - 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 '
'; - - $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; - + $checkbox = new EDD\HTML\CheckboxToggle( $checkbox_args ); + $checkbox->output(); + ?> +
+ + +
+ +
+ '_edd_button_behavior', + 'id' => 'edd_button_behavior', + 'selected' => get_post_meta( $download_id, '_edd_button_behavior', true ), + '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', + ) + ); + $select->output(); + ?> +
+
+ + + ID, true, $per_page, $offset ); - } else { - $page = 1; - $download_log = edd_get_file_download_log( $post->ID, true ); +function edd_render_product_notes_field( $post_id, $download = null ) { + // Check if the user can edit this specific download ID (post ID). + if ( ! current_user_can( 'edit_product', $post_id ) ) { + return; } - - $files = edd_get_download_files( $post->ID ); - - 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 + + $product_notes = $download ? $download->notes : false; + ?> +
+
+ + +
+

+ +

+
+ get_bundled_downloads(); +if ( ! $products ) { + $products = array( + 0 => '', + ); +} +$variable_pricing = $download->has_variable_prices(); +$row_classes = array( + 'edd-bundled-product-row', +); +if ( $variable_pricing ) { + $row_classes[] = 'has-variable-pricing'; +} +$prices = $download->get_prices(); +$bundle_options = EDD()->html->get_products( + array( + 'bundles' => false, + ) +); +?> + +
+
+
+ + + +
+ '_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', + 'bundles' => false, + 'exclude_current' => true, + 'show_option_empty' => __( 'Select a product', 'easy-digital-downloads' ), + 'show_option_all' => false, + ); + ?> +
+
+
+ + + + +
+
+ + +
+ output(); + ?> +
+
+ +
+ +
+ $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; + + $select = new 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, + ) + ); + $select->output(); + ?> +
+
+
+ + +
+ +
+
+ + +
+ +
+ +
+
+
+
diff --git a/includes/admin/downloads/views/metabox-files.php b/includes/admin/downloads/views/metabox-files.php new file mode 100644 index 00000000000..c83299c8483 --- /dev/null +++ b/includes/admin/downloads/views/metabox-files.php @@ -0,0 +1,51 @@ + + +
+
+
+ +
+ $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 ); + ?> + +
+ +
+ + + +
+ +
+ + + +
+ +
+ +
+
+
+
diff --git a/includes/admin/emails/email-summary/class-edd-email-summary-admin.php b/includes/admin/emails/email-summary/class-edd-email-summary-admin.php new file mode 100644 index 00000000000..1381a8baa66 --- /dev/null +++ b/includes/admin/emails/email-summary/class-edd-email-summary-admin.php @@ -0,0 +1,99 @@ + '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..ac5c0bc980f --- /dev/null +++ b/includes/admin/import/class-batch-import-downloads.php @@ -0,0 +1,550 @@ +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 bool + */ + 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 ( FileSystem::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 string + */ + 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..f91fd34dd08 --- /dev/null +++ b/includes/admin/import/class-batch-import-payments.php @@ -0,0 +1,650 @@ +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 ) ); + } + + $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(); + + // Add a meta key to indicate this payment was imported. + edd_add_order_meta( $payment->ID, '_edd_imported', time() ); + + /** + * Fires after an order is created during a batch import of payments. + * + * @since 3.3.0 + * @param int $payment_id The ID of the payment. + */ + do_action( 'edd_batch_import_order_created', $payment->ID ); + } + + 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..c8ffdb17475 --- /dev/null +++ b/includes/admin/import/class-batch-import.php @@ -0,0 +1,362 @@ + database fields + * + * @since 2.6 + */ + public $field_mapping = array(); + + /** + * Is the import done? + * + * @since 2.6 + * @var bool + */ + public $done = false; + + /** + * Get things started + * + * @param $_step int The step to process + * @since 2.6 + */ + public function __construct( $_file = '', $_step = 1 ) { + + $this->step = $_step; + 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', EDD\Utils\FileSystem::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 ( ! FileSystem::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 ( ! FileSystem::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( + /* translators: 1: URL to view imported items, 2: Import type label */ + __( 'Import complete! View imported %2$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..f39eacba400 --- /dev/null +++ b/includes/admin/notes/note-actions.php @@ -0,0 +1,136 @@ + $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; + } + + if ( ! current_user_can( 'edit_shop_payments' ) ) { + 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 { + /* translators: %s: email address */ + 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-cancelled': + edd_update_payment_status( $id, 'cancelled' ); + break; + + case 'resend-receipt': + $order = edd_get_order( $id ); + $order_receipt = EDD\Emails\Registry::get( 'order_receipt', array( $order ) ); + $order_receipt->send(); + 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' ); + +/** + * Recalculate the values of an order. + * + * @since 3.3.4 + * @param array $data + */ +function edd_recalculate_order_values( $data ) { + if ( ! current_user_can( 'edit_shop_payments' ) ) { + return; + } + if ( empty( $data['_wpnonce'] ) || ! wp_verify_nonce( $data['_wpnonce'], 'edd_recalculate_order_nonce' ) ) { + return; + } + $order = edd_get_order( $data['id'] ); + $recalculated = $order->recalculate(); + + $url = remove_query_arg( 'edd-action', wp_get_referer() ); + if ( $recalculated ) { + $url = add_query_arg( 'edd-message', 'order_recalculated', $url ); + } else { + $url = add_query_arg( 'edd-message', 'order_not_recalculated', $url ); + } + + edd_redirect( $url ); +} +add_action( 'edd_recalculate_order', 'edd_recalculate_order_values' ); 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; + } + } + + 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' => '', + '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, $order ); + + if ( empty( $value ) ) { + $value = '—'; + } + + break; + case 'status': + $value = edd_get_order_status_badge( $order->status, $order ); + 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: %s: the order number */ + esc_html( sprintf( _x( 'Select %s', 'Number: The order ID in alpha numeric representation', '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 ) && $this->order_receipts_enabled() ) { + $url = esc_url( + add_query_arg( + array( + 'edd-action' => 'email_links', + 'purchase_id' => absint( $order->id ), + ), + $this->base_url + ) + ); + $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' ), + '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; + } + + /** + * 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. + */ + return apply_filters( 'edd_payments_table_views', $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 ( 'all' === $gateway ) { + $gateway = null; + } + + if ( 'all' === $mode ) { + $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 Search query. + * @param array $args The array of arguments. + * @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; + } + + // Support searching by Stripe Payment Method. + if ( is_string( $search ) && ( false !== strpos( $search, 'payment-method:' ) ) ) { + $search = trim( str_replace( 'payment-method:', '', $search ) ); + $meta_query = array( + array( + 'key' => 'stripe_payment_method_type', + 'value' => $search, + 'compare' => '=', + ), + ); + // if the $args already have a meta_query, we need to merge the new meta_query with the existing one. + if ( isset( $args['meta_query'] ) ) { + $args['meta_query'] = array_merge( $args['meta_query'], $meta_query ); + } else { + $args['meta_query'] = $meta_query; + } + + 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 Which side of the table we're rendering. + * @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 ); + ?> + +
+
+ order_receipts_enabled ) ) { + $email = edd_get_email_by( 'email_id', 'order_receipt' ); + + $this->order_receipts_enabled = $email && $email->status; + } + + return $this->order_receipts_enabled; + } +} diff --git a/includes/admin/payments/class-refund-items-table.php b/includes/admin/payments/class-refund-items-table.php new file mode 100644 index 00000000000..b729c86b741 --- /dev/null +++ b/includes/admin/payments/class-refund-items-table.php @@ -0,0 +1,755 @@ + '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: %s: The product name */ + esc_html( sprintf( _x( 'Select %s', 'Title: The title of the current download product', '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' ) . '

' . + /* translators: %s: Documentation URL */ + '

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

' . + '

' . sprintf( + /* translators: %s: Upgrade URL */ + __( '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( + /* translators: %s: the prefix needed to search by customer - This should remain untranslated `customer:` */ + __( 'The customer\'s name or ID prefixed by %s', 'easy-digital-downloads' ), + 'customer:' + ) . '
  • +
  • ' . sprintf( + /* translators: %s: the prefix needed to search by user - This should remain untranslated `user:` */ + __( 'A user\'s ID prefixed by %s', 'easy-digital-downloads' ), + 'user:' + ) . '
  • +
  • ' . sprintf( + /* translators: %s: the prefix needed to search by Order ID - This should remain untranslated `#` */ + __( 'The %1$s ID prefixed by %2$s', 'easy-digital-downloads' ), + edd_get_label_singular(), + '#' + ) . '
  • +
  • ' . sprintf( + /* translators: %s: the prefix needed to search by discount code - This should remain untranslated `discount:` */ + __( 'The Discount Code prefixed by %s', 'easy-digital-downloads' ), + 'discount:' + ) . '
  • +
  • ' . sprintf( + /* translators: %s: the prefix needed to search by transaction ID - This should remain untranslated `txn:` */ + __( 'A transaction ID prefixed by %s', 'easy-digital-downloads' ), + 'txn:' + ) . '
  • +
  • ' . sprintf( + /* translators: %s: the prefix needed to search by Stripe Payment Method - This should remain untranslated `txn:` */ + __( 'A Stripe Payment Method prefixed by %s', 'easy-digital-downloads' ), + 'payment-method:' + ) . '
  • +
', + ) + ); + + $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..6f33cd5f08f --- /dev/null +++ b/includes/admin/payments/orders.php @@ -0,0 +1,1363 @@ + + +
+
+ +
+ +
+
+
+ + + + __( 'Checking this box will email the purchase receipt to the selected customer.', 'easy-digital-downloads' ), + ) + ); + $tooltip->output(); + ?> +
+
+
+ + +
+ + + 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' => __( 'Tools', '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 EDD\Orders\Order $order The order object. + */ +function edd_order_details_customer( $order ) { + $customer = edd_get_customer( $order->customer_id ); + $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. + ?> +
+
+ + + +
+ +
+
+ +
+ +
+
+ + +
+ + id ); + $user_info = $payment ? $payment->user_info : array(); + do_action( 'edd_payment_personal_details_list', $payment->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' ) + ); + $order_receipt = edd_get_email_by( 'email_id', 'order_receipt' ); + if ( $order_receipt && ! $order_receipt->status ) { + $help = __( 'Sending purchase receipts from Easy Digital Downloads has been disabled.', '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' ); + ?> + +
+
+ + +
+ + + + + +
+ +

+ +

+
+ +

+ status ) : ?> + 'email_links', + 'purchase_id' => absint( $order->id ), + ) + ); + ?> + href="" + + disabled + + id="" + class="button button-secondary" + > + + +

+ + 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 ); + + if ( current_user_can( 'edit_shop_payments' ) ) { + $link = wp_nonce_url( add_query_arg( 'edd-action', 'recalculate_order' ), 'edd_recalculate_order_nonce' ); + ?> +
+
+ +
+ +
+ + $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, $order ); ?> +
+ +
+
+ +
+ 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. ?> +
+ + + +
+ + +
+ + + status ) { + $dispute_id = edd_get_order_dispute_id( $order->id ); + if ( $dispute_id ) { + $dispute_id = apply_filters( "edd_payment_details_dispute_id_{$order->gateway}", $dispute_id, $order ); + ?> +
+ + +
+ + + +
+
+ +
+ +
+
+
+ + +
+
+
+ /> + + + __( '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.', 'easy-digital-downloads' ), + ) + ); + $tooltip->output(); + ?> +
+
+
+ + +
+ + date_actions_run ) ) { + $status = 'success'; + $label = __( 'Completed', 'easy-digital-downloads' ); + } elseif ( wp_next_scheduled( 'edd_after_payment_scheduled_actions', array( intval( $order->id ), false ) ) ) { + $status = 'processing'; + $label = __( 'Scheduled', 'easy-digital-downloads' ); + } + + $status_badge = new EDD\Utils\StatusBadge( + array( + 'status' => $status, + 'label' => $label, + ) + ); + + echo $status_badge->get(); + if ( empty( $status ) ) { + $tooltip = new EDD\HTML\Tooltip( + array( + 'content' => __( 'Deferred Actions were added in Easy Digital Downloads 2.8. Orders placed on prior versions will not have a deferred actions status. If this order was placed on a version of Easy Digital Downloads supporting Deferred Actions, please verify that WP Cron is able to be run.', 'easy-digital-downloads' ), + ) + ); + $tooltip->output(); + } + ?> +
+ + + id ); ?> +
+
+
+ + get_recovery_url(); + } + + $order_date = edd_get_edd_timezone_equivalent_date_from_utc( EDD()->utils->date( $order->date_created, 'utc', true ) ); + + ?> + +
+

+ +

+ +
+
+
+
+ +
+ +
+
+ + id ); + if ( $hold_reason ) { + $label = 'on_hold' === $order->status ? + __( 'On Hold Due To:', 'easy-digital-downloads' ) : + __( 'Original Hold Reason:', 'easy-digital-downloads' ); + if ( is_array( $hold_reason ) ) { + $hold_reason = array_map( 'edd_get_order_hold_reason_label', $hold_reason ); + $hold_reason = implode( ', ', $hold_reason ); + } else { + $hold_reason = edd_get_order_hold_reason_label( $hold_reason ); + } + ?> +
+ + +
+ '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, + $order + ); + $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. + * @param string $order_status Order status slug. + * @param EDD\Orders\Order $order Order object. Added in 3.2.7. + */ + $icon = apply_filters( 'edd_get_order_status_badge_icon', $status_badge->get_icon(), $order_status, $order ); + + return $status_badge->get( $icon ); +} diff --git a/includes/admin/payments/payments-history.php b/includes/admin/payments/payments-history.php index 1d16e4afc42..16e9354d97a 100755 --- a/includes/admin/payments/payments-history.php +++ b/includes/admin/payments/payments-history.php @@ -1,333 +1,224 @@ $label ) { + $tabs[ $type ] = array( + 'name' => $label, + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-payment-history', + 'order_type' => $type, + ), + ), + ); + } + $navigation = new EDD\Admin\Menu\SecondaryNavigation( + $tabs, + 'edd-payment-history', + array( + 'active_tab' => $active_tab, + 'show_search' => $show_search, + ) + ); + $navigation->render(); +} /** - * Payment History Page + * Retrieve the order pages. * - * Renders the payment history page contents. + * Used only by the primary tab navigation for orders. * - * @access private - * @since 1.0 - * @return void -*/ + * @since 3.0 + * + * @return array + */ +function edd_get_order_pages() { + + // Get types and setup return value + $types = edd_get_order_types(); + $retval = array(); + + // Loop through and get type IDs and labels + foreach ( $types as $type_id => $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' ) ); + + edd_orders_page_primary_nav( $active_tab, true ); + ?> +
+

+ '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..95c45ce84e3 --- /dev/null +++ b/includes/admin/payments/refunds.php @@ -0,0 +1,375 @@ + absint( $refund->parent ), + 'page' => 'edd-payment-history', + 'view' => 'view-order-details', + ) + ); +?> + +
+
+ +
+ + + +
+ +
+ __( 'A refund is a read-only record to help balance your store's books.', 'easy-digital-downloads' ), + ) + ); + $tooltip->output(); + ?> +
+ +
+
+ + $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 ), + ) + ); + /* translators: %s: refund link */ + 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 ); + ?> +
+
+ +
+
+
+ +
+ +has_pass() ) { + + $url = edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => '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/reporting/class-api-requests-logs-list-table.php b/includes/admin/reporting/class-api-requests-logs-list-table.php new file mode 100755 index 00000000000..ee02a4c7dd9 --- /dev/null +++ b/includes/admin/reporting/class-api-requests-logs-list-table.php @@ -0,0 +1,134 @@ + __( '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'; + } + + /** + * 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 Arguments for getting logs. + * + * @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..f297c093280 --- /dev/null +++ b/includes/admin/reporting/class-base-logs-list-table.php @@ -0,0 +1,634 @@ + '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 Position of the tablenav: 'top' or 'bottom'. + */ + 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. + */ + public function get_logs( $log_query = array() ) { + return array(); + } + + /** + * Get the total number of items. + * + * @since 3.0 + * @param array $log_query Arguments for getting logs. + * @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. + $query_args = 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 ) ) { + $query_args['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 ) ) { + $query_args['user_id'] = $user->ID; + } else { + // This is a fallback to help ensure an invalid email will produce zero results. + $query_args['search'] = $search; + } + } else { + // All other logs are linked to customers. + $customer = edd_get_customer_by( 'email', $search ); + if ( ! empty( $customer->id ) ) { + $query_args['customer_id'] = $customer->id; + } else { + // This is a fallback to help ensure an invalid email will produce zero results. + $query_args['search'] = $search; + } + } + } elseif ( 'api_requests' === $this->log_type && 32 === strlen( $search ) ) { + // Look for an API key. + $query_args['api_key'] = $search; + } elseif ( 'api_requests' === $this->log_type && stristr( $search, 'token:' ) ) { + // Look for an API token. + $query_args['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 ) ) { + $query_args['user_id'] = $user->ID; + } else { + $query_args['search'] = $search; + } + } else { + // All other logs are linked to customers. + $customer = edd_get_customer( $search ); + if ( ! empty( $customer->id ) ) { + $query_args['customer_id'] = $customer->id; + } elseif ( 'file_downloads' === $this->log_type ) { + $query_args['product_id'] = $search; + } else { + $query_args['search'] = $search; + } + } + } else { + if ( 'file_downloads' === $this->log_type ) { + $this->file_search = true; + } else { + $query_args['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 ) ) { + $query_args['date_created_query']['column'] = 'date_created'; + + // Start date. + if ( ! empty( $start_date ) ) { + $query_args['date_created_query'][] = array( + 'column' => 'date_created', + 'after' => \EDD\Utils\Date::parse( + date( // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date + 'Y-m-d H:i:s', + strtotime( "{$start_date} midnight" ) + ), + edd_get_timezone_id() + )->setTimezone( 'UTC' )->toDateTimeString(), + ); + } + + // End date. + if ( ! empty( $end_date ) ) { + $query_args['date_created_query'][] = array( + 'column' => 'date_created', + 'before' => \EDD\Utils\Date::parse( + date( // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date + 'Y-m-d H:i:s', + strtotime( "{$end_date} + 1 day" ) + ), + edd_get_timezone_id() + )->setTimezone( 'UTC' )->toDateTimeString(), + ); + } + } + + $query_args = array_filter( $query_args ); + + // Return query arguments. + return ( true === $paginate ) + ? $this->parse_pagination_args( $query_args ) + : $query_args; + } + + /** + * 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 ''; + } + + ?> + +

+ + + search_box ) { + submit_button( esc_html( $text ), 'button', false, false, array( 'ID' => 'search-submit' ) ); + } + ?> +

+ + ' . esc_html( $item['ip'] ) . ''; + } +} diff --git a/includes/admin/reporting/class-categories-reports-table.php b/includes/admin/reporting/class-categories-reports-table.php new file mode 100644 index 00000000000..f7c86eb15d0 --- /dev/null +++ b/includes/admin/reporting/class-categories-reports-table.php @@ -0,0 +1,380 @@ + '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..aa3159bfdf7 --- /dev/null +++ b/includes/admin/reporting/class-export.php @@ -0,0 +1,210 @@ +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..a98a026ba1b --- /dev/null +++ b/includes/admin/reporting/class-file-downloads-logs-list-table.php @@ -0,0 +1,274 @@ +' . esc_html( $column_value ) . ''; + } + + /** + * Gets the customer column. + * + * @since 3.3.6 + * @param array $item The log data. + * @return string + */ + protected function column_customer( $item ) { + $base_url = remove_query_arg( 'paged' ); + + return ! empty( $item['customer']->id ) + ? '' . esc_html( $item['customer']->name ) . '' + : '—'; + } + + /** + * Gets the payment ID column. + * + * @since 3.3.6 + * @param array $item The log data. + * @return string + */ + protected function column_payment_id( $item ) { + $number = edd_get_payment_number( $item['payment_id'] ); + + return ! empty( $number ) + ? '' . esc_html( $number ) . '' + : '—'; + } + + /** + * Gets the file column. + * + * @since 3.3.6 + * @param array $item The log data. + * @return string + */ + protected function column_file( $item ) { + return ! empty( $item['file'] ) + ? esc_html( $item['file'] ) + : '—'; + } + + /** + * 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 array $log_query Arguments for getting logs. + * + * @return array $logs_data Array of all the logs. + */ + public 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 Log record from the database. + * @param int $customer_id 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..9c196b72678 --- /dev/null +++ b/includes/admin/reporting/class-gateway-error-logs-list-table.php @@ -0,0 +1,138 @@ + + + + __( '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..b4de59355ee --- /dev/null +++ b/includes/admin/reporting/class-reports-sections.php @@ -0,0 +1,50 @@ +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..b4d8ca6ede5 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-file-downloads.php @@ -0,0 +1,169 @@ + __( '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..39231a05dbe --- /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 = ! empty( $order->user_id ) ? $order->user_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..006c66ad5bf --- /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' ), + 'tax' => __( 'Tax', '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 ); + + $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( $item->get_net_total() ), + 'tax' => edd_format_amount( $item->tax ), + '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..a006e91e449 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-taxed-orders.php @@ -0,0 +1,323 @@ + __( '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..8a73609d1c6 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export.php @@ -0,0 +1,380 @@ +file_system = FileSystem::get_fs(); + + $exports_dir = edd_get_exports_dir(); + $this->filetype = '.csv'; + $file_date = date( 'Y-m-d' ); + $file_hash = substr( wp_hash( 'edd-' . $this->export_type . '-export', 'nonce' ), 0, 8 ); + $this->filename = sprintf( + 'edd-%1$s-export-%2$s-%3$s%4$s', + $this->export_type, + $file_date, + $file_hash, + $this->filetype + ); + $this->file = trailingslashit( $exports_dir ) . $this->filename; + + if ( ! $this->file_system->is_writable( $exports_dir ) ) { + $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 ( FileSystem::file_exists( $this->file ) ) { + $this->file_system->delete( $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 .= count( $cols ) === $i ? '' : ','; + ++$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 not, skip it. + if ( array_key_exists( $col_id, $cols ) ) { + $column = is_numeric( $column ) || ! empty( $column ) ? $column : ''; + $row_data .= '"' . addslashes( preg_replace( '/\"/', "'", $column ) ) . '"'; + $row_data .= count( $cols ) === $i ? '' : ','; + ++$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 ( FileSystem::file_exists( $this->file ) ) { + + if ( ! $this->file_system->is_writable( $this->file ) ) { + $this->is_writable = false; + } + + $file = FileSystem::get_contents( $this->file ); + + } else { + + $this->file_system->put_contents( $this->file, '' ); + $this->file_system->chmod( $this->file, 0664 ); + + } + + return $file; + } + + /** + * Append data to export file + * + * @since 2.4 + * @param string $data The data to add to the file. + * @return void + */ + protected function stash_step_data( $data = '' ) { + + $file = $this->get_file(); + $file .= $data; + $this->file_system->put_contents( $this->file, $file ); + + // If we have no rows after this step, mark it as an empty export. + $file_rows = FileSystem::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(); + + $this->file_system->delete( $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..77de3b24b4b --- /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 customers 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..0ca331b64f5 100755 --- a/includes/admin/reporting/graphing.php +++ b/includes/admin/reporting/graphing.php @@ -2,248 +2,829 @@ /** * 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 { + + foreach ( $temp_data['sales'] 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 ); -function edd_show_download_sales_graph($bgcolor = 'white') { - $downloads = get_posts(array('post_type' => 'download', 'posts_per_page' => -1)); - if($downloads) { - ob_start(); ?> - -
- $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 ); + + $hour++; + endwhile; + } elseif( $dates['range'] == 'this_week' || $dates['range'] == 'last_week' ) { + $num_of_days = cal_days_in_month( CAL_GREGORIAN, $dates['m_start'], $dates['year'] ); + + $report_dates = array(); + $i = 0; + while ( $i <= 6 ) { + if ( ( $dates['day'] + $i ) <= $num_of_days ) { + $report_dates[ $i ] = array( + 'day' => (string) $dates['day'] + $i, + 'month' => $dates['m_start'], + 'year' => $dates['year'], + ); + } else { + $report_dates[ $i ] = array( + 'day' => (string) $i, + 'month' => $dates['m_end'], + 'year' => $dates['year_end'], + ); + } + + $i++; + } -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() { + if ( ! current_user_can( 'view_shop_reports' ) ) { + return; + } + + $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..192c699c2f9 --- /dev/null +++ b/includes/admin/reporting/reports-callbacks.php @@ -0,0 +1,458 @@ +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 for the Reports Overview Earnings chart. + * + * @since 3.3.6 + * + * @return array + */ +function edd_overview_earnings_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(); + + if ( empty( $currency ) || 'convert' === $currency ) { + $column .= ' / rate'; + } + + $sql_clauses = Reports\get_sql_clauses( $period ); + + if ( ! empty( $currency ) && array_key_exists( strtoupper( $currency ), edd_get_currencies() ) ) { + $sql_clauses['where'] = $wpdb->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' ) + ) + ); + + $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']; + + $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; + } + } + } + + // 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 ), + ); +} + +/** + * The callback for the Reports Overview Sales Chart. + * + * @since 3.3.6 + * + * @return array + */ +function edd_overview_sales_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(); + + if ( empty( $currency ) || 'convert' === $currency ) { + $column .= ' / rate'; + } + + $sql_clauses = Reports\get_sql_clauses( $period ); + + if ( ! empty( $currency ) && array_key_exists( strtoupper( $currency ), edd_get_currencies() ) ) { + $sql_clauses['where'] = $wpdb->prepare( " AND currency = %s ", strtoupper( $currency ) ); + } + + // 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(); + + /** + * 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; + + // 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 ), + ); +} + +/** + * 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..cb9a5479b5f 100755 --- a/includes/admin/reporting/reports.php +++ b/includes/admin/reporting/reports.php @@ -1,47 +1,3322 @@ - -
-

- -

- - -

-

-
- 'edd-reports', + 'view' => 'overview', + ) + ); + + // 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_earnings_chart', + 'overview_sales_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_earnings_chart', + array( + 'label' => __( 'Earnings', 'easy-digital-downloads' ) . ' — ' . $label . ' — ' . __( 'Net', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => 'edd_overview_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.05)', + 'fill' => false, + 'type' => 'currency', + 'borderWidth' => 2, + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + ), + ), + ), + ) + ); + + $reports->register_endpoint( + 'overview_sales_chart', + array( + 'label' => __( 'Sales', 'easy-digital-downloads' ) . ' — ' . $label . ' — ' . __( 'Net', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => 'edd_overview_sales_chart', + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'sales' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(252,108,18,0.75)', + 'backgroundColor' => 'rgba(252,108,18,0.05)', + 'fill' => false, + 'borderWidth' => 2, + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'gridLines' => array( + 'drawBorder' => false, + ), + 'ticks' => array( + 'formattingType' => 'integer', + 'beginAtZero' => true, + 'suggestedMin' => 0, + 'maxTicksLimit' => 4, + 'padding' => 10, + ), + ), + ), + ), + ), + ), + ), + ) + ); + + } 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 = Reports\get_download_label( $download_data ); + + $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_earnings_chart', + 'download_sales_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', + ), + 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( + /* translators: %s: Download singular label */ + '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( + 'top_selling_downloads', + array( + /* translators: %s: Downloads plural label */ + '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_earnings_chart', + array( + 'label' => __( '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 + ); + + $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']; + + $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; + } + } + } + + // 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 ), + ); + }, + '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' => false, + 'borderWidth' => 2, + 'type' => 'currency', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + ), + ), + ), + ) + ); + + $reports->register_endpoint( + 'download_sales_chart', + array( + 'label' => __( 'Sales', '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'] ) ) + : ''; + + $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(); + + // 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; + + // 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 ), + ); + }, + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'sales' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(252,108,18,0.75)', + 'backgroundColor' => 'rgba(252,108,18,0.05)', + 'fill' => false, + 'borderWidth' => 2, + 'borderCapStyle' => 'round', + 'borderJoinStyle' => 'round', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'gridLines' => array( + 'drawBorder' => false, + ), + 'ticks' => array( + 'formattingType' => 'integer', + 'beginAtZero' => true, + 'suggestedMin' => 0, + 'maxTicksLimit' => 4, + 'padding' => 10, + ), + ), + ), + ), + ), + ), + ), + ) + ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_downloads_report' ); + +/** + * Register downloads taxonomy report and endpoints. + * + * @since 3.2.7 + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_downloads_taxonomy_report( $reports ) { + try { + $options = Reports\get_dates_filter_options(); + $dates = Reports\get_filter_value( 'dates' ); + + $hbh = Reports\get_dates_filter_hour_by_hour(); + $label = $options[ $dates['range'] ] . ( $hbh ? ' (' . edd_get_timezone_abbr() . ')' : '' ); + + $reports->add_report( + 'downloads_taxonomy', + array( + /* translators: %s: Downloads label */ + 'label' => sprintf( __( '%s Terms', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'priority' => 11, + 'icon' => 'category', + 'endpoints' => array( + 'tables' => array( + 'earnings_by_taxonomy', + ), + ), + 'filters' => array( 'dates' ), + ) + ); + + $reports->register_endpoint( + 'earnings_by_taxonomy', + array( + 'label' => __( 'Earnings By Term', '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 . 'src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php', + ), + ), + ), + ) + ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_downloads_taxonomy_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_orders_chart', + 'refunds_earnings_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(); + + return esc_html( + $stats->get_order_refund_count( + array( + 'range' => $dates['range'], + 'currency' => $currency, + ) + ) + ); + }, + '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(); + + return esc_html( + $stats->get_order_refund_count( + array( + 'range' => $dates['range'], + 'currency' => $currency, + 'fully_refunded' => true, + ) + ) + ); + }, + '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'], + '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_earnings_chart', + array( + 'label' => __( 'Refunded Revenue', 'easy-digital-downloads' ) . ' — ' . $label, + 'views' => array( + 'chart' => array( + 'data_callback' => 'edd_overview_refunds_chart', + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'amount' => array( + 'label' => __( 'Revenue', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(24,126,244,0.75)', + 'backgroundColor' => 'rgba(24,126,244,0.1)', + 'borderWidth' => 2, + 'type' => 'currency', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + ), + ), + ), + ) + ); + + $reports->register_endpoint( + 'refunds_orders_chart', + array( + 'label' => __( 'Refunded Orders', 'easy-digital-downloads' ) . ' — ' . $label, + 'views' => array( + 'chart' => array( + 'data_callback' => 'edd_overview_refunds_chart', + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'number' => array( + 'label' => _x( 'Number', 'Context: As in the total number of items', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(252,108,18,0.75)', + 'backgroundColor' => 'rgba(252,108,18,0.05)', + 'borderWidth' => 2, + 'borderCapStyle' => 'round', + 'borderJoinStyle' => 'round', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'gridLines' => array( + 'drawBorder' => false, + ), + 'ticks' => array( + 'formattingType' => 'integer', + 'beginAtZero' => true, + 'suggestedMin' => 0, + 'maxTicksLimit' => 4, + 'padding' => 10, + ), + ), + ), + ), + ), + ), + ), + ) + ); + } 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' ); + $order_status = Reports\get_filter_value( 'order_statuses' ); + + $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; + } + ); + + if ( 'stripe' === $gateway && 'payment-elements' === edds_get_elements_mode() ) { + $tables[] = 'stripe_payment_methods'; + } + + $charts = array_filter( + array( + 'gateway_sales_breakdown', + 'gateway_earnings_breakdown', + 'gateway_earnings_chart', + 'gateway_sales_chart', + ), + function ( $endpoint ) use ( $gateway ) { + switch ( $endpoint ) { + case 'gateway_earnings_chart': + case 'gateway_sales_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', 'order_statuses', 'taxes', 'currencies' ), + ) + ); + + $reports->register_endpoint( + 'sales_per_gateway', + array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency, $order_status ) { + $gateway = 'all' !== Reports\get_filter_value( 'gateways' ) + ? Reports\get_filter_value( 'gateways' ) + : ''; + + $stats = new EDD\Stats(); + $args = array( + 'range' => $dates['range'], + 'gateway' => $gateway, + 'currency' => $currency, + ); + + if ( ! empty( $order_status ) ) { + $args['status'] = array( $order_status ); + } + + return $stats->get_gateway_sales( $args ); + }, + '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, $order_status ) { + $gateway = 'all' !== Reports\get_filter_value( 'gateways' ) + ? Reports\get_filter_value( 'gateways' ) + : ''; + + $stats = new EDD\Stats(); + $args = array( + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'gateway' => $gateway, + 'output' => 'formatted', + 'currency' => $currency, + ); + + if ( ! empty( $order_status ) ) { + $args['status'] = array( $order_status ); + } + + return $stats->get_gateway_earnings( $args ); + }, + '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, $order_status ) { + $gateway = 'all' !== Reports\get_filter_value( 'gateways' ) + ? Reports\get_filter_value( 'gateways' ) + : ''; + + $stats = new EDD\Stats(); + $args = array( + 'range' => $dates['range'], + 'gateway' => $gateway, + 'output' => 'formatted', + 'type' => 'refund', + 'currency' => $currency, + ); + + if ( ! empty( $order_status ) ) { + $args['status'] = array( $order_status ); + } + + return $stats->get_gateway_earnings( $args ); + }, + '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, $order_status ) { + $gateway = 'all' !== Reports\get_filter_value( 'gateways' ) + ? Reports\get_filter_value( 'gateways' ) + : ''; + + $stats = new EDD\Stats(); + $args = array( + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'function' => 'AVG', + 'output' => 'formatted', + 'currency' => $currency, + ); + + if ( ! empty( $order_status ) ) { + $args['status'] = array( $order_status ); + } + + if ( ! empty( $gateway ) ) { + $args['gateway'] = $gateway; + return $stats->get_gateway_earnings( $args ); + } + + return $stats->get_order_earnings( $args ); + }, + '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', + ), + ), + ), + ) + ); + + $reports->register_endpoint( + 'stripe_payment_methods', + array( + 'label' => __( 'Stripe Payment Methods', 'easy-digital-downloads' ) . ' — ' . $options[ $dates['range'] ], + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\Gateways\\StripePaymentMethods', + ), + ), + ), + ) + ); + + $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, $order_status ) { + $stats = new EDD\Stats(); + $args = array( + 'range' => $dates['range'], + 'grouped' => true, + 'currency' => $currency, + ); + + if ( ! empty( $order_status ) ) { + $args['status'] = array( $order_status ); + } + + $g = $stats->get_gateway_sales( $args ); + + $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, $order_status ) { + $stats = new EDD\Stats(); + $args = array( + 'range' => $dates['range'], + 'grouped' => true, + 'exclude_taxes' => $exclude_taxes, + 'currency' => $currency, + ); + + if ( ! empty( $order_status ) ) { + $args['status'] = array( $order_status ); + } + + $g = $stats->get_gateway_earnings( $args ); + + $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_earnings_chart', + array( + 'label' => __( '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 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' ) + ) + ); + + $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']; + + $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' ) ) { + $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' ) ) { + $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' ) ) { + $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 ); + } + } + + $earnings = array_values( $earnings ); + + return array( + 'earnings' => $earnings, + ); + }, + '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' => false, + 'borderWidth' => 2, + 'type' => 'currency', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + ), + ), + ), + ) + ); + + $reports->register_endpoint( + 'gateway_sales_chart', + array( + 'label' => __( 'Sales', '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, {$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(); + + /** + * 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; + + // 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; + } + // 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; + } + // 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; + } + } + } + + // 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 ); + + return array( + 'sales' => $sales, + ); + }, + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'sales' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(252,108,18,0.75)', + 'backgroundColor' => 'rgba(252,108,18,0.05)', + 'fill' => false, + 'borderWidth' => 2, + 'borderCapStyle' => 'round', + 'borderJoinStyle' => 'round', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'gridLines' => array( + 'drawBorder' => false, + ), + 'ticks' => array( + 'formattingType' => 'integer', + 'beginAtZero' => true, + 'suggestedMin' => 0, + 'maxTicksLimit' => 4, + 'padding' => 10, + ), + ), + ), + ), + ), + ), + ), + ) + ); + } 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 = Reports\get_download_label( $download_data ); + + $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 = Reports\get_download_label( $download_data ); + + $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' => false, + 'borderWidth' => 2, + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'gridLines' => array( + 'drawBorder' => false, + ), + 'ticks' => array( + 'formattingType' => 'integer', + 'beginAtZero' => true, + 'suggestedMin' => 0, + 'maxTicksLimit' => 4, + 'padding' => 10, + ), + ), + ), + ), + ), + ), + ), + ) + ); + } 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' => false, + 'borderWidth' => 2, + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'gridLines' => array( + 'drawBorder' => false, + ), + 'ticks' => array( + 'formattingType' => 'integer', + 'beginAtZero' => true, + 'suggestedMin' => 0, + 'maxTicksLimit' => 4, + 'padding' => 10, + ), + ), + ), + ), + ), + ), + ), + ) + ); + } + } 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' => false, + 'borderWidth' => 2, + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'gridLines' => array( + 'drawBorder' => false, + ), + 'ticks' => array( + 'formattingType' => 'integer', + 'beginAtZero' => true, + 'suggestedMin' => 0, + 'maxTicksLimit' => 4, + 'padding' => 10, + ), + ), + ), + ), + ), + ), + ), + ) + ); + } 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' ) ) { + ?> +

+ : + +

+ 'edd-reports', + 'view' => 'downloads_taxonomy', + ) + ) + ); +} +add_action( 'edd_show_downloads_taxonomy_report', 'edd_show_earnings_by_taxonomy_report' ); diff --git a/includes/admin/reporting/views/export-api-requests.php b/includes/admin/reporting/views/export-api-requests.php new file mode 100644 index 00000000000..018c7d68959 --- /dev/null +++ b/includes/admin/reporting/views/export-api-requests.php @@ -0,0 +1,38 @@ +
+

+
+

+
+
+ + + + + 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..91334f3959a --- /dev/null +++ b/includes/admin/reporting/views/export-customers.php @@ -0,0 +1,71 @@ +
+

+
+

+ +

+
+ 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: %s: Download plural 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..fdf7741af9c --- /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: %s: Download plural 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..b402634ed49 --- /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: %s: Download plural 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..da92f4b7a68 --- /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: %s: Download plural 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..60d02e67ad0 --- /dev/null +++ b/includes/admin/settings/contextual-help.php @@ -0,0 +1,156 @@ +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' ) . '

' . + /* translators: %s: Documentation URL */ + '

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

' . + '

' . sprintf( + /* translators: %s: Upgrade URL */ + __( '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..66a019afec7 --- /dev/null +++ b/includes/admin/settings/display-settings.php @@ -0,0 +1,84 @@ + +
+
+

+ +

+
+
+ +
+

+ +

+ 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', 'easy-digital-downloads' ) + ), + '', + '' + ); + ?> +

+ +
+ $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 && class_exists( '\\EDD\\Admin\\Settings\\Register' ) ) { + $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 ); + + // If we have a class for this tab, use it to sanitize the input. + // Normalize the tab name to be a class name. + $tab_class = EDD\Utils\Convert::snake_to_camel( $tab ); + $tab_class = 'EDD\\Admin\\Settings\\Sanitize\\Tabs\\' . $tab_class; + if ( class_exists( $tab_class ) ) { + $input = $tab_class::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 ); + + // If we have a class for this section, use it to sanitize the input. + // Normalize the section name to be a class name. + $section_class = $tab_class . '\\' . EDD\Utils\Convert::snake_to_camel( $section ); + if ( class_exists( $section_class ) ) { + $input = $section_class::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 ); + + // See if we have a setting specific sanitization class for this type. + $type_class = 'EDD\\Settings\\Sanitize\\Types\\' . EDD\Utils\Convert::snake_to_camel( $type ); + if ( class_exists( $type_class ) ) { + $output[ $key ] = $type_class::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', + ) + ); +} + +/** + * Sanitize text fields + * + * @since 1.8 + * @since 3.3.3 Converted to use setting type sanitization class. + * + * @param string $input The field value. + * + * @return string $input Sanitized value + */ +function edd_sanitize_text_field( $input = '' ) { + return EDD\Settings\Sanitize\Types\Text::sanitize( $input ); +} + +/** + * 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 ); + } elseif ( is_array( $class ) ) { + $class = array_values( array_map( 'sanitize_html_class', $class ) ); + $class = implode( ' ', array_unique( $class ) ); + } + + return $class; +} + +/** + * 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 ]; + } elseif ( $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' ), + '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; + if ( ! empty( $args['desc'] ) ) { + $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 ) { + $class = isset( $args['size'] ) ? $args['size'] : 'regular'; + $class .= '-text'; + if ( ! empty( $args['field_class'] ) ) { + $class .= ' ' . $args['field_class']; + } + $number_args = array( + 'value' => edd_get_option( $args['id'], $args['std'] ), + 'desc' => $args['desc'], + 'id' => 'edd_settings[' . esc_attr( $args['id'] ) . ']', + 'name' => 'edd_settings[' . esc_attr( $args['id'] ) . ']', + 'class' => $class, + 'readonly' => ! empty( $args['readonly'] ), + 'disabled' => ! empty( $args['disabled'] ), + 'placeholder' => ! empty( $args['placeholder'] ) ? $args['placeholder'] : '', + ); + if ( isset( $args['faux'] ) && true === $args['faux'] ) { + $number_args['readonly'] = true; + $number_args['name'] = ''; + } + if ( isset( $args['min'] ) ) { + $number_args['min'] = $args['min']; + } + if ( isset( $args['max'] ) ) { + $number_args['max'] = $args['max']; + } + if ( isset( $args['step'] ) ) { + $number_args['step'] = $args['step']; + } + if ( isset( $args['datalist'] ) ) { + $number_args['datalist'] = $args['datalist']; + } + $number = new EDD\HTML\Number( $number_args ); + + echo apply_filters( 'edd_after_setting_output', $number->get(), $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(); + } + } + + $args['name'] = 'edd_settings[' . esc_attr( $args['id'] ) . ']'; + $args['id'] = 'edd_settings[' . esc_attr( $args['id'] ) . ']'; + if ( ! empty( $args['multiple'] ) ) { + $args['name'] .= '[]'; + } + $args['selected'] = $value; + $args['class'] = edd_sanitize_html_class( $args['field_class'] ); + if ( ! isset( $args['show_option_all'] ) ) { + $args['show_option_all'] = false; + } + if ( ! isset( $args['show_option_none'] ) ) { + $args['show_option_none'] = false; + } + + $html = EDD()->html->select( $args ); + if ( ! empty( $args['desc'] ) ) { + $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 ) { + $uploader = new EDD\HTML\Upload( + array( + 'value' => edd_get_option( $args['id'], $args['std'] ), + 'desc' => $args['desc'], + 'id' => 'edd_settings[' . esc_attr( $args['id'] ) . ']', + 'name' => 'edd_settings[' . esc_attr( $args['id'] ) . ']', + ) + ); + + echo apply_filters( 'edd_after_setting_output', $uploader->get(), $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'] ); + $class = edd_sanitize_html_class( $args['field_class'] ); + $html = ''; + + if ( empty( edd_get_shop_states() ) ) { + $placeholder = __( 'Enter a region', 'easy-digital-downloads' ); + $html = ''; + } else { + $placeholder = isset( $args['placeholder'] ) + ? $args['placeholder'] + : ''; + if ( $args['chosen'] ) { + $class .= ' edd-select-chosen'; + if ( is_rtl() ) { + $class .= ' chosen-rtl'; + } + } + $html = EDD()->html->region_select( + array( + 'name' => 'edd_settings[' . edd_sanitize_key( $args['id'] ) . ']', + 'id' => 'edd_settings[' . edd_sanitize_key( $args['id'] ) . ']', + 'class' => $class, + 'show_option_empty' => $placeholder, + 'placeholder' => $placeholder, + ), + '', + $edd_option + ); + } + $html .= ! empty( $args['desc'] ) ? '

' . 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 ) { + ?> + + + + get(); + if ( ! empty( $args['desc'] ) ) { + $html .= '

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

'; + } + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Set manage_shop_settings as the cap required to save EDD settings pages + * + * @since 1.9 + * @return string capability required + */ +function edd_set_settings_cap() { + return 'manage_shop_settings'; +} +add_filter( 'option_page_capability_edd_settings', 'edd_set_settings_cap' ); + +/** + * Maybe attach a tooltip to a setting + * + * @since 1.9 + * @param string $html + * @param type $args + * @return string + */ +function edd_add_setting_tooltip( $html = '', $args = array() ) { + + // Tooltip has title & description. + if ( ! empty( $args['tooltip_title'] ) && ! empty( $args['tooltip_desc'] ) ) { + $args = wp_parse_args( + $args, + array( + 'tooltip_title' => '', + 'tooltip_desc' => '', + 'tooltip_dashicon' => '', + ) + ); + $tooltip = new EDD\HTML\Tooltip( + array( + 'title' => $args['tooltip_title'], + 'content' => $args['tooltip_desc'], + 'dashicon' => $args['tooltip_dashicon'], + ) + ); + $tooltip = $tooltip->get(); + $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)); - - ?> - - -
diff --git a/includes/blocks/views/checkout/purchase-form/gateways.php b/includes/blocks/views/checkout/purchase-form/gateways.php new file mode 100644 index 00000000000..7a214dafb92 --- /dev/null +++ b/includes/blocks/views/checkout/purchase-form/gateways.php @@ -0,0 +1,57 @@ +
+ + + +
+ + +
+ + +
+ $gateway ) { + $checked = $gateway_id === $default ? 'checked' : checked( $gateway_id, $payment_mode, false ); + $class = 'edd-gateway-option'; + if ( $checked ) { + $class .= ' edd-gateway-option-selected'; + } + + ?> + + +
+ + +
+ +
+ + + +
+ + +
+ +
diff --git a/includes/blocks/views/checkout/purchase-form/logged-in.php b/includes/blocks/views/checkout/purchase-form/logged-in.php new file mode 100644 index 00000000000..ce04dc69a61 --- /dev/null +++ b/includes/blocks/views/checkout/purchase-form/logged-in.php @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/includes/blocks/views/checkout/purchase-form/login.php b/includes/blocks/views/checkout/purchase-form/login.php new file mode 100644 index 00000000000..3729fd83c94 --- /dev/null +++ b/includes/blocks/views/checkout/purchase-form/login.php @@ -0,0 +1,32 @@ +
+ +
+ +
+ +
+
+
+ +
+ + + + +
+
+
+ + +
+
diff --git a/includes/blocks/views/checkout/purchase-form/personal-info.php b/includes/blocks/views/checkout/purchase-form/personal-info.php new file mode 100644 index 00000000000..01b283ceff0 --- /dev/null +++ b/includes/blocks/views/checkout/purchase-form/personal-info.php @@ -0,0 +1,56 @@ + +
+ + +
+ + +

+
+ + +
+ + /> +

+
+ + +
+ + /> +

+
+ +
diff --git a/includes/blocks/views/checkout/purchase-form/register.php b/includes/blocks/views/checkout/purchase-form/register.php new file mode 100644 index 00000000000..d7578c6a037 --- /dev/null +++ b/includes/blocks/views/checkout/purchase-form/register.php @@ -0,0 +1,38 @@ + +
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ +
+
diff --git a/includes/blocks/views/downloads/content.php b/includes/blocks/views/downloads/content.php new file mode 100644 index 00000000000..574f2e251c0 --- /dev/null +++ b/includes/blocks/views/downloads/content.php @@ -0,0 +1,30 @@ +
+ +
+ %s', + absint( get_the_ID() ), + esc_html__( 'Free', 'easy-digital-downloads' ) + ); + } elseif ( edd_has_variable_prices( get_the_ID() ) ) { + echo wp_kses_post( edd_price_range( get_the_ID() ) ); + } else { + edd_price( get_the_ID(), true ); + } + ?> +
+ +
diff --git a/includes/blocks/views/downloads/downloads.php b/includes/blocks/views/downloads/downloads.php new file mode 100644 index 00000000000..51d105fb315 --- /dev/null +++ b/includes/blocks/views/downloads/downloads.php @@ -0,0 +1,35 @@ + +
+ have_posts() ) { + $downloads->the_post(); + ?> +
+ +
+ +
+ + get_the_ID(), + 'align' => $block_attributes['purchase_link_align'], + 'show_price' => (bool) $block_attributes['show_price'], + 'direct' => 'direct' === edd_get_download_button_behavior( get_the_ID() ), + ); + echo EDD\Blocks\Downloads\buy_button( $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> + diff --git a/includes/blocks/views/downloads/header.php b/includes/blocks/views/downloads/header.php new file mode 100644 index 00000000000..f99b129e331 --- /dev/null +++ b/includes/blocks/views/downloads/header.php @@ -0,0 +1,14 @@ + +
+ %s', + esc_url( get_the_permalink() ), + esc_html( get_the_title() ) + ); + } + do_action( 'edd_blocks_downloads_after_entry_title', $block_attributes ); + ?> +
diff --git a/includes/blocks/views/downloads/image.php b/includes/blocks/views/downloads/image.php new file mode 100644 index 00000000000..fd41b15ff77 --- /dev/null +++ b/includes/blocks/views/downloads/image.php @@ -0,0 +1,27 @@ + + + aria-hidden="true" + tabindex="-1" + + > + "align{$block_attributes['image_alignment']} edd-blocks__download-image", + 'alt' => '', + ) +); +if ( $block_attributes['image_link'] ) : + ?> + + 'download', + 'format' => '?paged=%#%', + 'current' => max( 1, $query_args['paged'] ), + 'total' => $downloads->max_num_pages, +); + +if ( is_single() ) { + $args['base'] = get_permalink() . '%#%'; +} else { + $big = 999999; + $search_for = array( $big, '#038;' ); + $replace_with = array( '%#%', '&' ); + $args['base'] = str_replace( $search_for, $replace_with, get_pagenum_link( $big ) ); +} + +edd_pagination( $args ); diff --git a/includes/blocks/views/forms/login.php b/includes/blocks/views/forms/login.php new file mode 100644 index 00000000000..0dfad001678 --- /dev/null +++ b/includes/blocks/views/forms/login.php @@ -0,0 +1,48 @@ + + diff --git a/includes/blocks/views/forms/lost-password.php b/includes/blocks/views/forms/lost-password.php new file mode 100644 index 00000000000..717b189ef99 --- /dev/null +++ b/includes/blocks/views/forms/lost-password.php @@ -0,0 +1,31 @@ + +

+ +

+
+
+ +
+ +
+
+
+ + + + + +
+ +
diff --git a/includes/blocks/views/forms/registration.php b/includes/blocks/views/forms/registration.php new file mode 100644 index 00000000000..b3560b0dd5d --- /dev/null +++ b/includes/blocks/views/forms/registration.php @@ -0,0 +1,77 @@ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + + +
+
+
+
+
+ + +
+
+
+ +
+ +
+
+ +

+
+ + + + + + +
+ +
diff --git a/includes/blocks/views/forms/reset-password.php b/includes/blocks/views/forms/reset-password.php new file mode 100644 index 00000000000..06ee5f3774e --- /dev/null +++ b/includes/blocks/views/forms/reset-password.php @@ -0,0 +1,46 @@ + +

+ +

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

+
+ + + + + + +
+
diff --git a/includes/blocks/views/orders/credits.php b/includes/blocks/views/orders/credits.php new file mode 100644 index 00000000000..675e76e4954 --- /dev/null +++ b/includes/blocks/views/orders/credits.php @@ -0,0 +1,22 @@ +get_credits(); +if ( empty( $credits ) ) { + return; +} +?> +
+
:
+
+description ) ) { + $label = $credit->description; + } + ?> +
+
+
total ), $order->currency ) ); ?>
+
+ get_discounts(); +if ( ! $order_discounts ) { + return; +} + +$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})"; + } + ?> +
+
+
total ), $order->currency ) ); ?>
+
+ product_id, $item->price_id ); +if ( $block_attributes['hide_empty'] && empty( $download_files ) ) { + return; +} +$order = edd_get_order( $item->order_id ); +$classes = array( + 'edd-blocks__row', + 'edd-order-item__product', +); +if ( $block_attributes['search'] && edd_is_pro() ) { + $classes[] = 'edd-pro-search__product'; +} + +$registered_columns = EDD\Blocks\Orders\get_user_downloads_block_columns(); +?> +
+ $column ) { + $row = $column['row']; + + $classes = array( + 'edd-blocks__row-column', + 'edd-blocks__row-column--' . $column_id, + ); + + if ( ! empty( $row['classes'] ) ) { + $classes = array_merge( $classes, $row['classes'] ); + } + + ?> +
+ $name, + 'order_item' => $item, + 'order' => $order, + 'block_attributes' => $block_attributes, + 'download_files' => $download_files, + ); + /** + * Renders a column in the user downloads block. + * + * To add a new column, use the `edd_blocks_user_download_columns` filter. + * + * @since 2.0.6 + * @param array $action_args The arguments to pass to the hook. + * @param string $name The name of the product. + * @param EDD\Orders\Order_Item $item The order item. + * @param EDD\Orders\Order $order The order object. + * @param array $block_attributes The block attributes. + * @param array $download_files The download files. + */ + do_action( 'edd_blocks_user_downloads_block_column_' . $column_id, $action_args ); + ?> +
+ +
diff --git a/includes/blocks/views/orders/fees.php b/includes/blocks/views/orders/fees.php new file mode 100644 index 00000000000..3b0af109764 --- /dev/null +++ b/includes/blocks/views/orders/fees.php @@ -0,0 +1,22 @@ +get_fees(); +if ( empty( $fees ) ) { + return; +} +?> +
+
:
+
+description ) ) { + $label = $fee->description; + } + ?> +
+
+
subtotal, $order->currency ) ); ?>
+
+ +
+
+ +
+ +
+
+
+ + + + +
+
diff --git a/includes/blocks/views/orders/orders.php b/includes/blocks/views/orders/orders.php new file mode 100644 index 00000000000..854a6ea7bea --- /dev/null +++ b/includes/blocks/views/orders/orders.php @@ -0,0 +1,41 @@ + +

+ +
+ +
+
+
+ get_number() ) + ); + ?> +
+
status ) ); ?>
+
+
+
+ utils->date( $order->date_created, null, true )->toDateTimeString() ) ); ?> +
+
+ total, $order->currency ) ); ?> +
+
+
+ +
+
+ +
diff --git a/includes/blocks/views/orders/pagination.php b/includes/blocks/views/orders/pagination.php new file mode 100644 index 00000000000..7cac2e7b248 --- /dev/null +++ b/includes/blocks/views/orders/pagination.php @@ -0,0 +1,16 @@ + +
+ 'purchase_history', + 'total' => ceil( $count / $number ), + ) + ); + ?> +
diff --git a/includes/blocks/views/orders/pending.php b/includes/blocks/views/orders/pending.php new file mode 100644 index 00000000000..c855d02d027 --- /dev/null +++ b/includes/blocks/views/orders/pending.php @@ -0,0 +1,19 @@ + + + + diff --git a/includes/blocks/views/orders/receipt-files.php b/includes/blocks/views/orders/receipt-files.php new file mode 100644 index 00000000000..e550d7d003b --- /dev/null +++ b/includes/blocks/views/orders/receipt-files.php @@ -0,0 +1,73 @@ +is_deliverable() || ! edd_receipt_show_download_files( $item->product_id, $edd_receipt_args, $item ) ) { + return; +} +$download_files = edd_get_download_files( $item->product_id, $item->price_id ); +?> +
    + $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; + ?> +
diff --git a/includes/blocks/views/orders/receipt-item.php b/includes/blocks/views/orders/receipt-item.php new file mode 100644 index 00000000000..93afc74ec64 --- /dev/null +++ b/includes/blocks/views/orders/receipt-item.php @@ -0,0 +1,49 @@ +
+
+
+ product_name ); + + if ( ! empty( $item->status ) && 'complete' !== $item->status ) { + echo ' – ' . esc_html( edd_get_status_label( $item->status ) ); + } + ?> +
+ product_id ); + if ( ! empty( $notes ) ) : + ?> +
+ +
+ + product_id ) ); ?> +
+ + +
+ + quantity ); ?> +
+ +
+
+ total, $order->currency ) ); ?> +
+
diff --git a/includes/blocks/views/orders/receipt-items.php b/includes/blocks/views/orders/receipt-items.php new file mode 100644 index 00000000000..6b339fd7c4a --- /dev/null +++ b/includes/blocks/views/orders/receipt-items.php @@ -0,0 +1,32 @@ + + +
+ +
+ + get_items(); +if ( empty( $order_items ) ) { + return; +} +?> + +

+ +
+ $item ) { + // Skip this item if we can't view it. + if ( ! apply_filters( 'edd_user_can_view_receipt_item', true, $item ) ) { + continue; + } + include 'receipt-item.php'; + } + ?> +
diff --git a/includes/blocks/views/orders/totals.php b/includes/blocks/views/orders/totals.php new file mode 100644 index 00000000000..d6a9068f7b9 --- /dev/null +++ b/includes/blocks/views/orders/totals.php @@ -0,0 +1,78 @@ + +
+ +
+
:
+
get_number() ); ?>
+
+ +
+
:
+
status ) ); ?>
+
+ + +
+
:
+
payment_key ); ?>
+
+ + + +
+
:
+
gateway, $order ) ); ?>
+
+ + +
+
:
+
utils->date( $order->date_created, null, true )->toDateTimeString() ) ); ?>
+
+ +
+
:
+
+ id ) ); ?> +
+
+ + + + tax > 0 ) : ?> +
+
:
+
id ) ); ?>
+
+ + + +
+
:
+
id ) ); ?>
+
+ + +
+ +
+ +
diff --git a/includes/blocks/views/search.php b/includes/blocks/views/search.php new file mode 100644 index 00000000000..cb890cf551c --- /dev/null +++ b/includes/blocks/views/search.php @@ -0,0 +1,9 @@ +
+ + +
diff --git a/includes/blocks/views/terms.php b/includes/blocks/views/terms.php new file mode 100644 index 00000000000..d493c4d96e2 --- /dev/null +++ b/includes/blocks/views/terms.php @@ -0,0 +1,46 @@ + +
+ terms as $term ) : + $term_description = term_description( $term->term_id, $term->taxonomy ); + $attachment_id = get_term_meta( $term->term_id, 'download_term_image', true ); + ?> +
+ +
+ + + +
+ + + +
+

name ); ?>

+ + (count ); ?>) + +
+ + + +
+ +
+ +
+ +
diff --git a/includes/cart-actions.php b/includes/cart-actions.php deleted file mode 100755 index 2ab68c3828c..00000000000 --- a/includes/cart-actions.php +++ /dev/null @@ -1,64 +0,0 @@ - $download_id, 'options' => $options); - } else { - $cart = array(array('id' => $download_id, 'options' => $options)); - } - - $_SESSION['edd_cart'] = $cart; - - do_action( 'edd_post_add_to_cart', $download_id, $options ); - - // clear all the checkout errors, if any - edd_clear_errors(); - - return count($cart) - 1; - } -} - - -/** - * Remove From Cart - * - * Removes a download from the shopping cart. - * Uses edd_get_cart_contents(). - * - * @access public - * @since 1.0 - * @param $cart_key INT the cart key to remove - * @return array - of updated cart items -*/ - -function edd_remove_from_cart($cart_key) { - $cart = edd_get_cart_contents(); - - do_action( 'edd_pre_remove_from_cart', $cart_key ); - - if(!is_array($cart)) { - return true; // empty cart - } else { - unset($cart[$cart_key]); - } - $_SESSION['edd_cart'] = $cart; - - do_action( 'edd_post_remove_from_cart', $cart_key ); - - // clear all the checkout errors, if any - edd_clear_errors(); - - return $cart; // the updated cart items -} - - -/** - * Item in Cart - * - * Checks to see if an item is already in the cart. - * Uses edd_get_cart_contents(). - * - * @access public - * @since 1.0 - * @param $download_id - INT the ID number of the download to remove - * @return boolean -*/ - -function edd_item_in_cart($download_id) { - $cart_items = edd_get_cart_contents(); - if(!is_array($cart_items)) { - return false; // empty cart - } else { - foreach($cart_items as $item) { - if($item['id'] == $download_id) { - return true; - } - } - } -} - - -/** - * Get Item Position in Cart - * - * Gets the position of an item in the cart. - * Uses edd_get_cart_contents(). - * - * @access public - * @since 1.0.7.2 - * @param $download_id - INT the ID number of the download to remove - * @return $position - INT position of the item in the cart -*/ - -function edd_get_item_position_in_cart($download_id) { - $cart_items = edd_get_cart_contents(); - if(!is_array($cart_items)) { - return false; // empty cart - } else { - foreach($cart_items as $postion => $item) { - if($item['id'] == $download_id) { - return $postion; - } - } - } -} - - -/** - * Get Cart Item Quantity - * - * Gets the quanity for an item in the cart. - * - * @access public - * @since 1.0 - * @param $item INT the download (cart item) ID number - * @return $position - INT position of the item in the cart -*/ - -function edd_get_cart_item_quantity($item) { - $cart = edd_get_cart_contents(); - $item_counts = array_count_values($cart); - $quantity = $item_counts[$item]; - return $quantity; -} - - -/** - * Get Cart Item Price - * - * Gets the price of the cart item. - * - * @access public - * @since 1.0 - * @param $item INT the download ID number - * @param $options array optional parameters, used for defining variable prices - * @return string - price for this item -*/ - -function edd_get_cart_item_price($item_id, $options = array()) { - - $variable_pricing = get_post_meta( $item_id, '_variable_pricing', true) ; - $price = edd_get_download_price( $item_id ); - if( $variable_pricing && !empty( $options ) ) { - // if variable prices are enabled, retrieve the options - $prices = get_post_meta( $item_id, 'edd_variable_prices', true ); - if( $prices ) { - $price = $prices[$options['price_id']]['amount']; - } - } - return apply_filters( 'edd_cart_item_price', $price ); -} - - -/** - * Get Price Name - * - * Gets the name of the specified price option, - * for variable pricing only. - * - * @access public - * @since 1.0 - * @param $item INT the download ID number - * @param $options array optional parameters, used for defining variable prices - * @return string - the name of the price option -*/ - -function edd_get_price_name($item_id, $options = array()) { - - $variable_pricing = get_post_meta($item_id, '_variable_pricing', true); - if($variable_pricing && !empty($options)) { - // if variable prices are enabled, retrieve the options - $prices = get_post_meta($item_id, 'edd_variable_prices', true); - if($prices) { - $name = $prices[$options['price_id']]['name']; - } - return $name; - } - return false; -} - - -/** - * Get Cart Amount - * - * Gets the total price amount in the cart. - * uses edd_get_cart_contents(). - * - * @access public - * @since 1.0 - * @return string - the name of the price option -*/ - -function edd_get_cart_amount() { - $cart_items = edd_get_cart_contents(); - $amount = 0; - if($cart_items) { - foreach($cart_items as $item) { - $item_price = edd_get_cart_item_price( $item['id'], $item['options'] ); - $amount = $amount + $item_price; - } - if(isset($_POST['edd-discount']) && $_POST['edd-discount'] != '') { - // discount is validated before this function runs, so no need to check for it - $amount = edd_get_discounted_amount($_POST['edd-discount'], $amount); - } - - return number_format( $amount, 2 ); - } - return 0; -} - - - -/** - * Get Purchase Summary - * - * Retrieves the purchase summary. - * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_get_purchase_summary($purchase_data, $email = true) { - - $summary = ''; - if($email) { - $summary .= $purchase_data['user_email'] . ' - '; - } - foreach($purchase_data['downloads'] as $download) { - $summary .= get_the_title($download['id']) . ', '; - } - $summary = substr($summary, 0, -2); - - return $summary; -} - - -/** - * Get Cart Content Details - * - * Retrieves the cart contnet details. - * - * @access public - * @since 1.0 - * @return array -*/ - -function edd_get_cart_content_details() { - $cart_items = edd_get_cart_contents(); - $details = array(); - if($cart_items) { - foreach($cart_items as $key => $item) { - $details[$key] = array( - 'name' => get_the_title($item['id']), - 'id' => $item['id'], - 'item_number' => $item, - 'price' => edd_get_cart_item_price($item['id'], $item['options']), - 'quantity' => 1, - ); - } - } - if(!empty($details)) { - return $details; - } - return false; -} - - -/** - * Add Collection to Cart - * - * Adds all downloads within a taxonomy term to the cart. - * - * @access public - * @since 1.0.6 - * @param $taxonomy string - the name of the taxonomy - * @param $terms mixed - the slug or id of the term from which to add ites, or an array of terms - * @return array of IDs for each item added to the cart -*/ - -function edd_add_collection_to_cart($taxonomy, $terms) { - - if(!is_string($taxonomy)) return false; - - $field = is_int($terms) ? 'id' : 'slug'; - - $cart_item_ids = array(); - - $args = array( - 'post_type' => 'download', - 'posts_per_page' => -1, - $taxonomy => $terms - ); - - $items = get_posts($args); - if($items) { - - foreach($items as $item) { - edd_add_to_cart($item->ID); - $cart_item_ids[] = $item->ID; - } - } - return $cart_item_ids; -} - - -/** - * Remove Item URL - * - * Returns the URL to remove an item. - * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_remove_item_url($cart_key, $post, $ajax = false) { - global $post; - $current_page = edd_get_current_page_url(); - $remove_url = add_query_arg( array('cart_item' => $cart_key, 'edd_action' => 'remove' ), $current_page); - return apply_filters('edd_remove_item_url', $remove_url); -} - - - -/** - * Show Added To Cart Messages - * - * Renders the added to cart messages. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_show_added_to_cart_messages($download_id) { - if( isset( $_POST['edd_action'] ) && $_POST['edd_action'] == 'add_to_cart' ) { - $alert = sprintf( __('You have successfully added %s to your shopping cart.', 'edd'), get_the_title( $download_id ) ); - $alert .= ' ' . __('Checkout.', 'edd') . ''; - echo '
' . $alert . '
'; - } -} -add_action('edd_after_download_content', 'edd_show_added_to_cart_messages'); - - -/** - * Get Checkout URI - * - * Retrieves the URL of the checkout page. - * - * @access public - * @since 1.0.8 - * @return mixed - the full URL to the checkout page, if present, NULL if it doesn't exist -*/ - -function edd_get_checkout_uri() { - global $edd_options; - $uri = isset( $edd_options['purchase_page'] ) ? get_permalink( $edd_options['purchase_page'] ) : NULL; - return apply_filters( 'edd_get_checkout_uri', $uri ); -} - - -/** - * Checks if on checkout page - * - * Determines if the current page is the checkout page - * - * @access public - * @since 1.1.2 - * @return bool - true if on the page, false otherwise -*/ - -function edd_is_checkout() { - global $edd_options; - return isset( $edd_options['purchase_page'] ) ? is_page( $edd_options['purchase_page'] ) : false; -} - - -/** - * Empty Cart - * - * Empties the cart. - * - * @access public - * @since 1.0 - * @return void -*/ - -function edd_empty_cart() { - $_SESSION['edd_cart'] = NULL; -} - - -/** - * Store Purchase Data in Sessions - * - * Used for storing info about purchase - * - * @access public - * @since 1.1.5 - * @return void -*/ - -function edd_set_purchase_session( $purchase_data ) { - $_SESSION['edd_purchase_info'] = $purchase_data; -} - - -/** - * Retrieve Purchase Data from Session - * - * Used for retrieving info about purchase - * after completing a purchase - * - * @access public - * @since 1.1.5 - * @return array / false -*/ - -function edd_get_purchase_session() { - return isset( $_SESSION['edd_purchase_info'] ) ? $_SESSION['edd_purchase_info'] : false; -} - - -// make sure a session is started -if(!session_id()){ - add_action( 'init', 'session_start', -1 ); -} \ No newline at end of file diff --git a/includes/cart-template.php b/includes/cart-template.php deleted file mode 100755 index 71f907b3104..00000000000 --- a/includes/cart-template.php +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - ' . edd_get_price_name($id, $item['options']); - } - $remove = '' . __('remove', 'edd') . ''; - $item = '
  • ' . $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..2471578ed36 --- /dev/null +++ b/includes/cart/actions.php @@ -0,0 +1,198 @@ +query_vars['edd-add'] ) ) { + $download_id = absint( $wp_query->query_vars['edd-add'] ); + 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'] ); + 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 The data sent via the add to cart form. + */ +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'] ) ) { + if ( ! isset( $options['quantity'] ) ) { + $options['quantity'] = array(); + } elseif ( ! is_array( $options['quantity'] ) ) { + $options['quantity'] = (array) $options['quantity']; + } + 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', 'edd_download_quantity' ) ); + $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', 'edd_download_quantity' ) ) ); + } +} +add_action( 'edd_add_to_cart', 'edd_process_add_to_cart' ); + +/** + * Process the 'Remove from Cart' request. + * + * @since 1.0 + * + * @param array $data The data sent via the remove from cart form. + */ +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', 'edd_remove_from_cart_nonce' ) ) ); +} +add_action( 'edd_remove', 'edd_process_remove_from_cart' ); + +/** + * Process the Remove fee from Cart request. + * + * @since 2.0 + * + * @param array $data The data sent via the remove fee from cart form. + */ +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 array $data The data sent via the collection purchase form. + */ +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..d3f9291c725 --- /dev/null +++ b/includes/cart/class-edd-cart.php @@ -0,0 +1,1544 @@ +cart_session = new EDD\Sessions\Cart( $this ); + add_action( 'init', array( $this, 'setup_cart' ), 1 ); + } + + /** + * Sets up cart components. + * + * @since 2.7 + * @return void + */ + public function setup_cart() { + if ( ! EDD\Utils\Request::is_request( 'frontend' ) ) { + return; + } + + static $done = false; + if ( $done ) { + return; + } + $this->cart_session->get_contents(); + $this->get_contents(); + $this->get_contents_details(); + $this->get_all_fees(); + $this->cart_session->get_discounts(); + $this->get_quantity(); + + $done = true; + } + + /** + * 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; + } + + /** + * Get cart contents + * + * @since 2.7 + * @return array List of cart contents. + */ + public function get_contents() { + // Check for the session contents if the cart contents haven't been loaded yet and the session is available. + if ( ! did_action( 'edd_cart_contents_loaded_from_session' ) && $this->cart_session ) { + $this->cart_session->get_contents(); + } + + $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 -= $this->format_amount( $tax ); + } + + $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' => $this->format_amount( $item_price ), + 'quantity' => $quantity, + 'discount' => $this->format_amount( $discount ), + 'subtotal' => $this->format_amount( $subtotal ), + 'tax' => $this->format_amount( $tax ), + 'fees' => $fees, + 'price' => $this->format_amount( $total ), + ); + + 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->cart_session->get_discounts(); + $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 ); + + // Not a download product. + if ( empty( $download->ID ) ) { + return; + } + + // Do not allow draft/pending to be purchased if can't edit. Fixes #1056. + if ( ! $download->can_purchase() ) { + return; + } + + 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 ); + + 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'] ) ) { + $quantities_enabled = edd_item_quantities_enabled() && ! edd_download_quantities_disabled( $download_id ); + 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'] ) ) { + $prices = $download->get_prices(); + // Process multiple price options at once. + foreach ( $options['price_id'] as $key => $price ) { + $price_id = preg_replace( '/[^0-9\.-]/', '', $price ); + if ( ! isset( $prices[ $price_id ] ) ) { + $price_id = $download->get_default_price_id(); + } + $items[] = array( + 'id' => $download_id, + 'options' => array( + 'price_id' => $price_id, + ), + 'quantity' => is_array( $quantity ) && isset( $quantity[ $key ] ) ? $quantity[ $key ] : $quantity, + ); + } + } else { + // Sanitize price IDs. + foreach ( $options as $key => $option ) { + if ( 'price_id' === $key && ! is_null( $option ) ) { + $prices = $download->get_prices(); + $price_id = preg_replace( '/[^0-9\.-]/', '', $option ); + if ( ! isset( $prices[ $price_id ] ) ) { + $price_id = $download->get_default_price_id(); + } + $options[ $key ] = $price_id; + } + } + + // 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 { + // Generate a unique disposable hash for this item. + $to_add['hash'] = md5( $to_add['id'] . serialize( $to_add['options'] ) . time() . wp_rand( 0, 1000 ) ); + $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() { + $this->cart_session->empty_cart(); + + 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; + } + + $discounts = $this->get_discounts(); + if ( $discounts ) { + $key = array_search( $code, $discounts, true ); + + if ( false !== $key ) { + unset( $discounts[ $key ] ); + } + + $this->discounts = implode( '|', array_values( $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; + } + + /** + * 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. + return $this->format_amount( $item_price - $discounted_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() ) { + /* translators: %s: tax rate formatted as a percentage */ + $label .= sprintf( __( 'includes %s tax', 'easy-digital-downloads' ), edd_get_formatted_tax_rate() ); + } else { + /* translators: %s: tax rate formatted as a percentage */ + $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 ) { + $price = false; + if ( ! empty( $options ) && isset( $options['price_id'] ) ) { + $price = $prices[ $options['price_id'] ]['amount']; + } + } + } + + 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 ); + } + + $price = false !== $price ? $this->format_amount( $price ) : $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', $this->format_amount( $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', $this->format_amount( $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', $this->format_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', $this->format_amount( $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', $this->format_amount( $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', $this->format_amount( 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( $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', $this->format_amount( $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; + } + + if ( is_user_logged_in() ) { + $saved_cart = get_user_meta( get_current_user_id(), 'edd_saved_cart', true ); + if ( ! $saved_cart ) { + return false; + } + + return EDD()->session->get( 'edd_cart' ) === $saved_cart; + } + + if ( ! isset( $_COOKIE['edd_saved_cart'] ) ) { + return false; + } + + // If there is a saved cart and it doesn't match the current cart, return true. + return json_decode( stripslashes( $_COOKIE['edd_saved_cart'] ), true ) !== EDD()->session->get( 'edd_cart' ); + } + + /** + * 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 { + $expiration = time() + WEEK_IN_SECONDS; + $cart = json_encode( $cart ); + EDD\Utils\Cookies::set( 'edd_saved_cart', $cart, $expiration ); + EDD\Utils\Cookies::set( 'edd_cart_token', $token, $expiration ); + } + + // 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 ); + EDD\Utils\Cookies::set( 'edd_saved_cart' ); + EDD\Utils\Cookies::set( 'edd_cart_token' ); + } + + $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() ) ); + } + + /** + * Format an amount for the cart/currency. + * + * @since 3.3.6 + * @param string $amount The amount to format. + * @return string + */ + private function format_amount( $amount ) { + return edd_format_amount( $amount, true, '', 'data' ); + } + + /** + * Populate the cart with the data stored in the session. + * + * @since 2.7 + * @deprecated 3.3.0 + * @return void + */ + public function get_contents_from_session() { + $this->cart_session->get_contents(); + } + + /** + * Populate the discounts with the data stored in the session. + * + * @since 2.7 + * @deprecated 3.3.0 + * @return void + */ + public function get_discounts_from_session() { + $this->cart_session->get_discounts(); + } + + /** + * Remove all discount codes. + * + * @since 2.7 + * @deprecated 3.3.0 + * @return void + */ + public function remove_all_discounts() { + $this->cart_session->remove_all_discounts(); + } +} diff --git a/includes/cart/functions.php b/includes/cart/functions.php new file mode 100755 index 00000000000..5c6b03f122d --- /dev/null +++ b/includes/cart/functions.php @@ -0,0 +1,626 @@ +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 Array of options, such as variable price. + * @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 Quantity to set. + * @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 int $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 sanitize_text_field( 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 Whether to echo or return the value. + * @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 Type of fees to check for. + * @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 Type of fees to get. + * @param int $download_id Download ID to get fees for. + * @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 array $purchase_data Purchase data. + * @param bool $email Whether to include the email address or not. + * @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 array $purchase_data 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|null + */ +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(); +} + +/** + * 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..095924fc8c1 --- /dev/null +++ b/includes/cart/template.php @@ -0,0 +1,317 @@ +'; + 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_template Echo or return. + * @return string Fully formatted cart + */ +function edd_shopping_cart( $echo_template = false ) { + ob_start(); + + do_action( 'edd_before_cart' ); + + edd_get_template_part( 'widget', 'cart' ); + + do_action( 'edd_after_cart' ); + + if ( $echo_template ) { + 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 = sanitize_text_field( 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'] ) && 'add_to_cart' === $_POST['edd_action'] ) { + if ( $download_id != absint( $_POST['download_id'] ) ) { + $download_id = absint( $_POST['download_id'] ); + } + + $alert = '
    ' + // Translators: %s: name of the download that was added to the cart. + . 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'])) { ?> - - 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 Extra query args to add to the URI. + * @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() { + + $order_id = filter_input( INPUT_GET, 'payment-id', FILTER_SANITIZE_NUMBER_INT ); + if ( empty( $order_id ) ) { + $order_id = EDD()->session->get( 'edd_resume_payment' ); + } + if ( empty( $order_id ) ) { + return; + } + if ( ! edd_is_failed_transaction_page() ) { + return; + } + + $order = edd_get_order( $order_id ); + if ( 'pending' === $order->status ) { + edd_update_order_status( $order->id, 'failed' ); + EDD()->session->set( 'edd_resume_payment', null ); + } +} +add_action( 'template_redirect', 'edd_listen_for_failed_payments' ); + +/** + * Check if a field is required + * + * @param string $field The field to check. + * @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 ) ) { + $banned_emails = array(); + } + + $is_email_banned = false; + foreach ( $banned_emails as $banned_email ) { + + $banned_email = strtolower( $banned_email ); + + if ( is_email( $banned_email ) ) { + // Complete email address. + $is_email_banned = $banned_email === $email; + + } elseif ( strpos( $banned_email, '.' ) === 0 ) { + // TLD block. + $is_email_banned = substr( $email, ( strlen( $banned_email ) * -1 ) ) === $banned_email; + + } else { + // Domain block. + $is_email_banned = false !== stristr( $email, $banned_email ); + } + + if ( true === $is_email_banned ) { + break; + } + } + + return apply_filters( 'edd_is_email_banned', $is_email_banned, $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 The content to filter. + * @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, true ) ) { + $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 Credit card 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 Credit card 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 Expiration month. + * @param string $exp_year Expiration 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..717b7d843d5 --- /dev/null +++ b/includes/checkout/pages.php @@ -0,0 +1,99 @@ + $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..8a21652612c --- /dev/null +++ b/includes/checkout/template.php @@ -0,0 +1,1113 @@ +'; + 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..3f3e2f31768 --- /dev/null +++ b/includes/class-base-object.php @@ -0,0 +1,141 @@ +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; + } + } +} 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..dbc19cb6319 --- /dev/null +++ b/includes/class-easy-digital-downloads.php @@ -0,0 +1,920 @@ +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\Handler(); + self::$instance->api = new EDD_API(); + self::$instance->debug_log = new EDD\Logging(); + self::$instance->utils = new EDD\Utilities(); + self::$instance->session = new EDD\Sessions\Handler(); + self::$instance->html = new EDD\HTML\Elements(); + self::$instance->emails = new EDD_Emails(); + self::$instance->email_tags = new EDD\Emails\Tags\Handler(); + 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(); // phpcs:ignore + + // 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(); + } + + $tracking = edd_get_namespace( 'Telemetry\\Tracking' ); + self::$instance->tracking = new $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__, __( 'Method Not Allowed.', '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__, __( 'Method Not Allowed.', '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 Property to get. + * @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 The EDD loader 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.3.7' ); + } + + // 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(); + + add_action( 'init', function() { + $this->maybe_load_amazon(); + }, 5 ); + + // 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. + * + * @access public + * @since 3.0 + * @return void + */ + 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-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'; + + // 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-license-handler.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-stats.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-roles.php'; + + // Deprecated Functions. + if ( FileSystem::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'; + require_once EDD_PLUGIN_DIR . 'includes/deprecated/classes.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/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 ( FileSystem::file_exists( $stripe ) ) { + require_once $stripe; + } + + // Logs. + require_once EDD_PLUGIN_DIR . 'includes/logs/functions.php'; + 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/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'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/disputes.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/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/recapture.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/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 ( FileSystem::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/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/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() { // phpcs:ignore + add_action( + 'rest_api_init', + function () { + $endpoints = array( + '\\EDD\\API\\v3\\Notifications', + ); + + foreach ( $endpoints as $endpointClassName ) { // phpcs:ignore + $endpointNamePieces = explode( '\\', $endpointClassName ); // phpcs:ignore + $endpointName = end( $endpointNamePieces ); // phpcs:ignore + + require_once EDD_PLUGIN_DIR . 'includes/api/v3/' . $endpointName . '.php'; // phpcs:ignore + + if ( class_exists( $endpointClassName ) ) { // phpcs:ignore + $endpoint = new $endpointClassName(); // phpcs:ignore + $endpoint->register(); + } + } + } + ); + } + + /** + * Maybe load the Amazon Payments gateway. + * If the gateway is not set up, this will do nothing. + * + * @since 3.2.0 + * @return void + */ + private function maybe_load_amazon() { + if ( ! edd_is_gateway_setup( 'amazon', true ) ) { + return; + } + + require_once EDD_PLUGIN_DIR . 'includes/gateways/amazon-payments.php'; + PayWithAmazon\EDD_Amazon_Payments::getInstance(); + } + } +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() { // phpcs:ignore + 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..73801ddcbea --- /dev/null +++ b/includes/class-edd-cache-helper.php @@ -0,0 +1,145 @@ +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', '' ); + + /* translators: %s: EDD version */ + WP_CLI::line( sprintf( __( 'You are running EDD version: %s', 'easy-digital-downloads' ), EDD_VERSION ) ); + WP_CLI::line( + "\n" + . sprintf( + /* translators: %s: The status of test mode, either Enabled or Disabled */ + __( 'Test mode is: %s', 'easy-digital-downloads' ), + ( edd_is_test_mode() + ? __( 'Enabled', 'easy-digital-downloads' ) + : __( 'Disabled', 'easy-digital-downloads' ) + ) + ) + ); + WP_CLI::line( + sprintf( + /* translators: %s: The status of AJAX, either Enabled or Disabled */ + __( 'AJAX is: %s', 'easy-digital-downloads' ), + ( edd_is_ajax_enabled() + ? __( 'Enabled', 'easy-digital-downloads' ) + : __( 'Disabled', 'easy-digital-downloads' ) + ) + ) + ); + WP_CLI::line( + sprintf( + /* translators: %s: The status of guest checkouts, either Enabled or Disabled */ + __( 'Guest checkouts are: %s', 'easy-digital-downloads' ), + ( edd_no_guest_checkout() + ? __( 'Disabled', 'easy-digital-downloads' ) + : __( 'Enabled', 'easy-digital-downloads' ) + ) + ) + ); + WP_CLI::line( + sprintf( + /* translators: %s: The status of file downloads via symlink setting, either Enabled or Disabled */ + __( '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( + /* translators: %s: The status of the checkout page, either Valid or Invalid */ + __( '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( + /* translators: %s: The URL of the checkout page */ + __( 'Checkout URL is: %s', 'easy-digital-downloads' ), + ( ! empty( $purchase_page ) + ? get_permalink( $purchase_page ) + : __( 'Undefined', 'easy-digital-downloads' ) + ) + ) + ); + WP_CLI::line( + sprintf( + /* translators: %s: The URL of the success page */ + __( 'Success URL is: %s', 'easy-digital-downloads' ), + ( ! empty( $success_page ) + ? get_permalink( $success_page ) + : __( 'Undefined', 'easy-digital-downloads' ) + ) + ) + ); + WP_CLI::line( + sprintf( + /* translators: %s: The URL of the failure page */ + __( 'Failure URL is: %s', 'easy-digital-downloads' ), + ( ! empty( $failure_page ) + ? get_permalink( $failure_page ) + : __( 'Undefined', 'easy-digital-downloads' ) + ) + ) + ); + WP_CLI::line( + sprintf( + /* translators: %s: The download slug used in the WordPress Permalinks, defaults to /downloads */ + __( 'Downloads slug is: %s', 'easy-digital-downloads' ), + ( defined( 'EDD_SLUG' ) + ? '/' . EDD_SLUG + : '/downloads' + ) + ) + ); + WP_CLI::line( + "\n" + . sprintf( + /* translators: %s: The status of the taxes enabled setting, either Enabled or Disabled */ + __( 'Taxes are: %s', 'easy-digital-downloads' ), + ( edd_use_taxes() + ? __( 'Enabled', 'easy-digital-downloads' ) + : __( 'Disabled', 'easy-digital-downloads' ) + ) + ) + ); + /* translators: %s: The default tax rate formatted as a percentage */ + 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 ) { + /* translators: 1: The country code, 2: The state code, 3: The tax rate formatted as a percentage */ + WP_CLI::line( sprintf( __( 'Country: %1$s, State: %2$s, Rate: %3$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 ); + + /* translators: %s: The earnings formatted for the currency */ + WP_CLI::line( sprintf( __( 'Earnings: %s', 'easy-digital-downloads' ), $earnings ) ); + /* translators: %s: The sales count */ + 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' ) ); + /* translators: %d: The product ID (post ID) */ + WP_CLI::line( sprintf( _x( 'ID: %d', 'The Download/Product ID', 'easy-digital-downloads' ), $product['info']['id'] ) ); + /* translators: %s: The status of the product */ + WP_CLI::line( sprintf( _x( 'Status: %s', 'The Download/Product Status', 'easy-digital-downloads' ), $product['info']['status'] ) ); + /* translators: %s: The date the product was posted */ + WP_CLI::line( sprintf( __( 'Posted: %s', 'easy-digital-downloads' ), $product['info']['create_date'] ) ); + /* translators: %s: The product categories */ + WP_CLI::line( sprintf( __( 'Categories: %s', 'easy-digital-downloads' ), $categories ) ); + /* translators: %s: The product tags */ + WP_CLI::line( sprintf( __( 'Tags: %s', 'easy-digital-downloads' ), ( is_array( $tags ) ? '' : $tags ) ) ); + /* translators: %s: The product pricing */ + WP_CLI::line( sprintf( __( 'Pricing: %s', 'easy-digital-downloads' ), $pricing ) ); + /* translators: %s: The product sales count */ + WP_CLI::line( sprintf( _x( 'Sales: %s', 'The sales count for the product', 'easy-digital-downloads' ), $product['stats']['total']['sales'] ) ); + /* translators: %s: The product earnings formatted for the currency */ + WP_CLI::line( sprintf( _x( 'Earnings: %s', 'The product earnings', 'easy-digital-downloads' ), edd_format_amount( $product['stats']['total']['earnings'] . ' ' . edd_get_currency() ) ) ); + WP_CLI::line( '' ); + /* translators: %s: The product page slug */ + WP_CLI::line( sprintf( __( 'Slug: %s', 'easy-digital-downloads' ), $product['info']['slug'] ) ); + /* translators: %s: The product link */ + 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 ) { + /* translators: 1: The file name, 2: The file path */ + WP_CLI::line( ' ' . sprintf( __( 'File: %1$s (%2$s)', 'easy-digital-downloads' ), $file['name'], $file['file'] ) ); + + if ( isset( $file['condition'] ) && 'all' !== $file['condition'] ) { + /* translators: %s: The price assignment 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 ) { + /* translators: %d: The 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; + + } + + /* translators: 1: The number of customers created, %2$d: The time it took to create the customers */ + WP_CLI::line( WP_CLI::colorize( '%G' . sprintf( __( '%1$d customers created in %2$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' ) ); + /* translators: %d: The customer's user ID */ + WP_CLI::line( sprintf( __( 'Customer User ID: %s', 'easy-digital-downloads' ), $customer['info']['id'] ) ); + /* translators: %s: The customer's username */ + WP_CLI::line( sprintf( __( 'Username: %s', 'easy-digital-downloads' ), $customer['info']['username'] ) ); + /* translators: %s: The customer's display name */ + WP_CLI::line( sprintf( __( 'Display Name: %s', 'easy-digital-downloads' ), $customer['info']['display_name'] ) ); + + if ( array_key_exists( 'first_name', $customer ) ) { + /* translators: %s: The customer's first name */ + WP_CLI::line( sprintf( __( 'First Name: %s', 'easy-digital-downloads' ), $customer['info']['first_name'] ) ); + } + + if ( array_key_exists( 'last_name', $customer ) ) { + /* translators: %s: The customer's surname */ + WP_CLI::line( sprintf( __( 'Last Name: %s', 'easy-digital-downloads' ), $customer['info']['last_name'] ) ); + } + + /* translators: %s: The customer's email address */ + WP_CLI::line( sprintf( __( 'Email: %s', 'easy-digital-downloads' ), $customer['info']['email'] ) ); + + WP_CLI::line( '' ); + /* translators: %s: The customer's total purchases */ + WP_CLI::line( sprintf( __( 'Purchases: %s', 'easy-digital-downloads' ), $customer['stats']['total_purchases'] ) ); + /* translators: %s: The customer's total spent */ + WP_CLI::line( sprintf( __( 'Total Spent: %s', 'easy-digital-downloads' ), edd_format_amount( $customer['stats']['total_spent'] ) . ' ' . edd_get_currency() ) ); + /* translators: %s: The customer's total downloads */ + 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' ) ); + /* translators: %s: The purchase key */ + WP_CLI::line( sprintf( __( 'Purchase Key: %s', 'easy-digital-downloads' ), $sale['key'] ) ); + /* translators: %s: The customer's email address */ + WP_CLI::line( sprintf( __( 'Email: %s', 'easy-digital-downloads' ), $sale['email'] ) ); + /* translators: %s: The purchase date */ + WP_CLI::line( sprintf( __( 'Date: %s', 'easy-digital-downloads' ), $sale['date'] ) ); + /* translators: %s: The purchase subtotal */ + WP_CLI::line( sprintf( __( 'Subtotal: %s', 'easy-digital-downloads' ), edd_format_amount( $sale['subtotal'] ) . ' ' . edd_get_currency() ) ); + /* translators: %s: The purchase tax */ + 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 ) { + /* translators: 1: The fee amount, 2: The currency */ + WP_CLI::line( sprintf( __( ' Fee: %1$s - %2$s', 'easy-digital-downloads' ), edd_format_amount( $fee['amount'] ), edd_get_currency() ) ); + } + } + + /* translators: %s: The purchase total */ + WP_CLI::line( sprintf( __( 'Total: %s', 'easy-digital-downloads' ), edd_format_amount( $sale['total'] ) . ' ' . edd_get_currency() ) ); + WP_CLI::line( '' ); + /* translators: %s: The payment gateway used */ + 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'] . ')' : ''; + /* translators: 1: The product name, 2: The product price */ + WP_CLI::line( sprintf( __( ' Product: %1$s - %2$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' ) ); + /* translators: %s: The name of the discount */ + WP_CLI::line( sprintf( __( 'Name: %s', 'easy-digital-downloads' ), $discount['name'] ) ); + /* translators: %s: The code of the discount */ + 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(); + } + + /* translators: %s: The amount of the discount */ + WP_CLI::line( sprintf( __( 'Amount: %s', 'easy-digital-downloads' ), $amount ) ); + /* translators: %s: The number of uses of the discount */ + WP_CLI::line( sprintf( __( 'Uses: %s', 'easy-digital-downloads' ), $discount['uses'] ) ); + /* translators: %s: The maximum number of uses of the discount */ + WP_CLI::line( sprintf( __( 'Max Uses: %s', 'easy-digital-downloads' ), ( $discount['max_uses'] == '0' ? __( 'Unlimited', 'easy-digital-downloads' ) : $discount['max_uses'] ) ) ); + /* translators: %s: The start date of the discount */ + WP_CLI::line( sprintf( __( 'Start Date: %s', 'easy-digital-downloads' ), ( empty( $discount['start_date'] ) ? __( 'No Start Date', 'easy-digital-downloads' ) : $discount['start_date'] ) ) ); + /* translators: %s: The expiration date of the discount */ + WP_CLI::line( sprintf( __( 'Expiration Date: %s', 'easy-digital-downloads' ), ( empty( $discount['exp_date'] ) ? __( 'No Expiration', 'easy-digital-downloads' ) : $discount['exp_date'] ) ) ); + /* translators: %s: The status of the discount */ + WP_CLI::line( sprintf( _x( 'Status: %s', 'The status of the discount code', '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 ) { + /* translators: %s: The ID of the product required for this discount */ + WP_CLI::line( sprintf( __( ' Product: %s', 'easy-digital-downloads' ), $req_id ) ); + } + } + + WP_CLI::line( '' ); + + /* translators: %s: The type of discount if it is able to be used on all products. */ + WP_CLI::line( sprintf( __( 'Global Discount: %s', 'easy-digital-downloads' ), ( empty( $discount['global_discount'] ) ? 'False' : 'True' ) ) ); + /* translators: %s: The type of discount if it is a single use discount. */ + 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( + /* translators: %s: The query string */ + 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 = 'EDD'; + $lname = 'Guest'; + $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( + /* translators: %s: The status of the payment */ + __( "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 || ( ! empty( $prices ) && ! 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; + } + + // 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', + 'gateway' => $gateway, + ); + + $timestring = $this->get_order_timestring( $date, $range ); + if ( ! empty( $timestring ) ) { + $purchase_data['date_created'] = $timestring; + } + + $order_id = edd_build_order( $purchase_data ); + + // Ensure purchase receipts do not get sent. + edd_update_order_meta( $order_id, '_edd_should_send_order_receipt', false ); + edd_update_order_meta( $order_id, '_edd_should_send_admin_order_notice', false ); + + // Trigger payment status actions. + if ( 'pending' !== $status ) { + edd_update_order_status( $order_id, $status ); + } + + if ( ! empty( $timestring ) ) { + edd_update_order( + $order_id, + array( + 'date_completed' => $timestring, + ) + ); + } + + $progress->tick(); + } + + $progress->finish(); + + /* translators: %s: The number of orders created */ + WP_CLI::success( sprintf( __( 'Created %s orders', 'easy-digital-downloads' ), $number ) ); + + return; + } + + /** + * Create discount codes for your EDD site + * + * ## OPTIONS + * + * --number: The number of discounts to create + * + * ## EXAMPLES + * + * wp edd create_discounts --number=100 + */ + public function create_discounts( $args, $assoc_args ) { + $number = array_key_exists( 'number', $assoc_args ) ? absint( $assoc_args['number'] ) : 1; + + $progress = \WP_CLI\Utils\make_progress_bar( 'Creating Discount Codes', $number ); + + for ( $i = 0; $i < $number; $i++ ) { + $type = array( 'flat', 'percent' ); + $status = array( 'active', 'inactive', 'archived' ); + $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 ); + + // Randomly set a start date and time. + if ( rand( 0, 1 ) ) { + // Generate a start date randomly between 90 days ago and 90 days in the future. + $start_date_range = rand( -90, 90 ); + if ( 0 > $start_date_range ) { + $start_date_range = '+' . intval( $start_date_range ); + } + + $start_date_string = date( 'Y-m-d', strtotime( $start_date_range . ' days' ) ); + + $start_hour = rand( 0, 23 ); + $start_min = rand( 0, 59 ); + + $start_date = edd_get_utc_date_string( + EDD()->utils->get_date_string( + $start_date_string, + $start_hour, + $start_min + ) + ); + } + + // Randomly set a end date and time. + if ( rand( 0, 1 ) ) { + // Generate a start date randomly between 90 days ago and 90 days in the future. + $end_date_range = rand( 1, 90 ); + + if ( isset( $start_date_string ) ) { + $end_date_string = date( 'Y-m-d', strtotime( $start_date_string . ' +' . $end_date_range . ' days' ) ); + } else { + $end_date_string = date( 'Y-m-d', strtotime( '+' . $end_date_range . ' days' ) ); + } + + $end_hour = rand( 0, 23 ); + $end_min = rand( 0, 59 ); + + $end_date = edd_get_utc_date_string( + EDD()->utils->get_date_string( + $end_date_string, + $end_hour, + $end_min + ) + ); + } + + $max = mt_rand( 0, 100 ); + + $discount = array( + 'code' => md5( wp_generate_uuid4() ), + 'uses' => mt_rand( 0, $max ), + 'max' => $max, + 'name' => 'Auto-Generated Discount #' . $i, + 'type' => $type[ $type_index ], + 'amount' => mt_rand( 10, 95 ), + 'min_price' => mt_rand( 1, 255 ), + 'status' => $status[ $status_index ], + 'product_reqs' => $product_condition[ $product_condition_index ], + ); + + if ( isset( $start_date ) ) { + $discount['start_date'] = $start_date; + } + + if ( isset( $end_date ) ) { + $discount['end_date'] = $end_date; + } + + edd_add_discount( $discount ); + + $progress->tick(); + } + + $progress->finish(); + + /* translators: %s: The number of discounts created */ + 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(); + + $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(); + + $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(); + + $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(); + + $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(); + + $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; + } + } + + // Now look for customers without email addresses in the emails table. + $sql_base = + "SELECT * + FROM {$wpdb->edd_customers} + WHERE email != '' + AND email NOT IN ( + SELECT email + FROM {$wpdb->edd_customer_email_addresses} + )"; + $sql = $sql_base . ' LIMIT 1'; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + + while ( $has_results ) { + $progress->tick(); + + // Query & count. + $sql = $sql_base . " LIMIT {$number}"; + $results = $wpdb->get_results( $sql ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + $customer_has_primary = edd_count_customer_email_addresses( + array( + 'customer_id' => $result->id, + 'type' => 'primary', + ) + ); + edd_add_customer_email_address( + array( + 'customer_id' => $result->id, + 'email' => $result->email, + 'date_created' => $result->date_created, + 'type' => $customer_has_primary ? 'secondary' : 'primary', + ) + ); + + // Tick the spinner... + $progress->tick(); + } + } 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(); + + $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(); + + $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 . ' ORDER BY ID DESC 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' ) ); + } + } + } elseif ( $destroy && isset( $assoc_args['conflicts'] ) ) { + $conflicting_orders = $this->find_conflicting_orders(); + if ( $conflicting_orders ) { + $full_migration = false; + $sql_base .= ' AND ID IN (' . implode( ',', $conflicting_orders ) . ')'; + } else { + WP_CLI::error( __( 'No conflicting orders were found.', 'easy-digital-downloads' ) ); + } + } + + if ( $full_migration ) { + $auto_increment = $wpdb->get_var( "SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" . DB_NAME . "' AND TABLE_NAME = '{$wpdb->edd_orders}';" ); + $last_payment = reset( $check_result ); + // If the auto_increment is less than the last payment ID, we need to reset the auto_increment. + if ( $auto_increment < $last_payment->ID ) { + $new_auto_increment = absint( $last_payment->ID ) + 1; + $wpdb->query( "ALTER TABLE {$wpdb->edd_orders} AUTO_INCREMENT = {$new_auto_increment}" ); + } + } + + // 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 = ! empty( $migrated->type ) && '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 ( ! empty( $migrated->date_created ) && $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 + ) + ); + } + /* translators: %d: The order ID. */ + WP_CLI::line( sprintf( __( 'Deleting order %d.', '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: %d: The 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' ); + edd_set_upgrade_complete( 'migrate_order_actions_date' ); + } + } elseif ( ! $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(); + } + + /** + * Migrate missing discounts. + * + * wp edd migrate_missing_discounts + * + * @since 3.2.0 + * @return void + */ + public function migrate_missing_discounts() { + $discounts = new EDD\CLI\Migration\Discounts(); + $discounts->migrate_missing(); + } + + /** + * Migrate missing customer emails. + * + * Command: wp edd migrate_missing_emails + * + * @since 3.2.2 + * @return void + */ + public function migrate_missing_emails() { + $emails = new EDD\CLI\Migration\CustomerEmails(); + $emails->migrate_missing(); + } + + /** + * Find payment IDs which were incorrectly migrated as refunds. + * + * @since 3.3.0 + * @return array + */ + public function find_conflicting_orders() { + global $wpdb; + + $results = $wpdb->get_results( + "SELECT p.* + FROM {$wpdb->posts} p + INNER JOIN {$wpdb->edd_orders} o ON p.id = o.id + WHERE p.post_type = 'edd_payment' + AND o.type = 'refund';" + ); + + $conflicting_orders = array(); + + if ( empty( $results ) ) { + WP_CLI::line( __( 'No conflicting orders were found.', 'easy-digital-downloads' ) ); + return $conflicting_orders; + } + foreach ( $results as $payment ) { + $conflicting_orders[] = $payment->ID; + WP_CLI::line( sprintf( 'Conflicting refund found for: %d', $payment->ID ) ); + } + + return $conflicting_orders; + } + + /** + * Gets a random first name. + * + * @return string + */ + 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 ) ) ]; + } + + /** + * Gets a random last name. + * + * @return string + */ + 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 ) ) ]; + } + + /** + * Gets a random domain. + * + * @return string + */ + protected function get_domain() { + $domains = array( + 'example', + 'edd', + 'rcp', + 'affwp', + ); + + return $domains[ rand( 0, ( count( $domains ) - 1 ) ) ]; + } + + /** + * Gets a random TLD. + * + * @return string + */ + protected function get_tld() { + $tlds = array( + 'local', + 'test', + 'example', + 'localhost', + 'invalid', + ); + + return $tlds[ rand( 0, ( count( $tlds ) - 1 ) ) ]; + } + + /** + * Gets a time string for an order. + * + * @since 3.3.7 + * @param mixed $date The date to use. + * @param int $range The range to use. + * @return false|string + */ + private function get_order_timestring( $date, $range ) { + + if ( empty( $date ) ) { + return false; + } + // 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 ); + + return date( 'Y-m-d H:i:s', $timestamp ); + } + + if ( is_numeric( $date ) ) { + return date( 'Y-m-d H:i:s', $date ); + } + + return date( 'Y-m-d H:i:s', strtotime( $date ) ); + } +} 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_order_ids(); + return implode( ',', $payment_ids ); + case 'order_ids': + return $this->get_order_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( + 'status' => 'active', + ); + + $args = wp_parse_args( $data, $defaults ); + $args = $this->sanitize_columns( $args ); + + if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) { + return false; + } + + $customer = edd_get_customer_by( 'email', $args['email'] ); + if ( $customer ) { + $this->setup_customer( $customer ); + + return $this->id; + } + + /** + * 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 ); + + // Add the customer. + $customer_id = edd_add_customer( $args ); + + if ( ! empty( $customer_id ) ) { + $email_address = edd_get_customer_email_address_by( 'email', $args['email'] ); + if ( ! $email_address ) { + 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 ); + } + + /** + * Fires after a customer is created. + * + * @param int $customer_id 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', $customer_id, $args ); + + return $customer_id; + } + + /** + * 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_addresses = edd_get_customer_email_addresses( + array( + 'customer_id' => $this->id, + 'email' => $email, + ) + ); + + if ( empty( $email_addresses ) ) { + $ret = false; + } else { + $found = count( $email_addresses ); + $removed = 0; + + foreach ( $email_addresses as $email_address ) { + $removed = (bool) edd_delete_customer_email_address( $email_address->id ) + ? $removed + 1 + : $removed; + } + + $ret = ( $found === $removed ); + } + + 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 order IDs. + $order_ids = $this->get_order_ids(); + + // Bail if no orders. + if ( empty( $payment_ids ) ) { + return; + } + + // Update order emails to primary email. + foreach ( $order_ids as $order_id ) { + edd_update_order( + $order_id, + array( + 'email' => $email, + ) + ); + } + } + + /** + * Get the payment ids of the customer in an array. + * + * @since 2.6 + * @deprecated 3.2 Use the get_order_ids method of the EDD_Customer object instead. + * + * @return array An array of payment IDs for the customer, or an empty array if none exist. + */ + public function get_payment_ids() { + _edd_deprecated_function( __METHOD__, '3.2', 'EDD_Customer::get_order_ids' ); + + return $this->get_order_ids(); + } + + /** + * Get an array of EDD_Payment objects from the payment_ids attached to the customer. + * + * @since 2.6 + * @deprecated 3.2 Use the get_orders method of the EDD_Customer object instead. + * + * @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() ) { + _edd_deprecated_function( __METHOD__, '3.2', 'EDD_Customer::get_orders' ); + + // Get payment IDs. + $payment_ids = $this->get_order_ids( $status ); + $payments = array(); + + // Bail if no IDs. + if ( empty( $payment_ids ) ) { + return $payments; + } + + // Get payments one at a time. + $payments = edd_get_payments( + array( + 'number' => count( $payment_ids ), + 'no_found_rows' => true, + 'id__in' => $payment_ids, + ) + ); + + return $payments; + } + + /** + * Get the customer's orders + * + * @since 3.2 + * + * @param array $status The status or statuses of the orders to get. + * + * @return array An array of EDD\Orders\Order objects. + */ + public function get_orders( $status = array() ) { + $order_args = array( + 'customer_id' => $this->id, + 'type' => 'sale', + ); + + if ( ! empty( $status ) ) { + $order_args['status__in'] = $status; + } + + // Get the order IDs for the user and the total number of orders. + $order_ids = $this->get_order_ids( $status ); + + // Since the `edd_get_orders` function limits to 30 by default, we need to override that. + $order_args['number'] = count( $order_ids ); + + // Get the customer's orders. + $orders = edd_get_orders( $order_args ); + + return $orders; + } + + /** + * Get the customer's order IDs. + * + * @since 3.2 + * + * @param array $status The status or statuses of the orders to get. + * + * @return array An array of order IDs. + */ + public function get_order_ids( $status = array() ) { + // Bail if no customer. + if ( empty( $this->id ) ) { + return array(); + } + + // Previously, a string was allowed in other methods, so let's ensure we have an array. + if ( is_string( $status ) ) { + $status = (array) $status; + } + + $count_args = array( + 'customer_id' => $this->id, + 'type' => 'sale', + ); + + if ( ! empty( $status ) ) { + $count_args['status__in'] = $status; + } + + // Get total orders. + $count = edd_count_orders( $count_args ); + + $order_args = array( + 'customer_id' => $this->id, + 'number' => $count, + 'fields' => 'ids', + 'no_found_rows' => true, + 'type' => 'sale', + ); + + if ( ! empty( $status ) ) { + $order_args['status__in'] = $status; + } + + // Get order IDs. + $ids = edd_get_orders( $order_args ); + + // Cast IDs to ints. + return ! empty( $ids ) ? array_map( 'absint', $ids ) : array(); + } + + /** + * 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 + * @since 3.2 Updated to use order objects. + * + * @param integer $order_id The Order 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( $order_id = 0, $update_stats = true ) { + + // Bail if no payment ID. + if ( empty( $order_id ) ) { + return false; + } + + // Get payment. + $order = edd_get_order( $order_id ); + + // Bail if payment does not exist. + if ( empty( $order ) ) { + return false; + } + + // Get all previous payment IDs + $order_ids = $this->get_order_ids(); + + // Bail if already detached. + if ( ! in_array( $order_id, $order_ids, true ) ) { + return true; + } + + // Only update stats when in a completed state. + if ( ! in_array( $order->status, edd_get_complete_order_statuses(), true ) ) { + $update_stats = false; + } + + do_action( 'edd_customer_pre_remove_payment', $order_id, $this->id, $this ); + + // Update the order. + $success = (bool) edd_update_order( + $order_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, $order_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.php b/includes/class-edd-db.php new file mode 100644 index 00000000000..270037e2823 --- /dev/null +++ b/includes/class-edd-db.php @@ -0,0 +1,237 @@ +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..bbf35955e58 --- /dev/null +++ b/includes/class-edd-discount.php @@ -0,0 +1,2067 @@ +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; + } + + /* translators: %s: discount property name */ + 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 'archived': + $label = __( 'Archived', '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 ( ! empty( $this->max_uses ) && $this->get_uses() >= $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 ) { + /* translators: %s: Minimum order amount, formatted for the currency */ + 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. + * @param false|array $cart_ids Cart item IDs or a specific array of download IDs to check (added in 3.2.0). + * @return bool Are required products in the cart? + */ + public function is_product_requirements_met( $set_error = true, $cart_ids = false ) { + $is_met = true; + $product_reqs = $this->get_product_reqs(); + // Filter out any empty values. + $product_reqs = array_map( 'strval', array_filter( array_values( $product_reqs ) ) ); + + if ( empty( $cart_ids ) ) { + $cart_items = edd_get_cart_contents(); + // Combine cart item IDs with their price IDs and convert to strings as we are going to deal with string values. + $cart_ids = array_map( + function ( $cart_item ) { + $price_id = isset( $cart_item['options']['price_id'] ) && is_numeric( $cart_item['options']['price_id'] ) ? $cart_item['options']['price_id'] : null; + + return ! is_null( $price_id ) ? $cart_item['id'] . '_' . $price_id : strval( $cart_item['id'] ); + }, + $cart_items + ); + } + + // Ensure we have requirements before proceeding. + if ( ! empty( $product_reqs ) ) { + // Set up an array of requirements with all values set to false. + $requirements = array_fill_keys( $product_reqs, false ); + foreach ( $cart_ids as $cart_id ) { + $key = $cart_id; + $requirement_met = array_key_exists( $cart_id, $requirements ); + + // If requirement is not met and the cart item is a variable product, check if the parent product is in the requirements. + if ( ! $requirement_met && preg_match( '/^(\d+)_(\d+)$/', $cart_id ) ) { + // Using absint to strip out anything after the _ in price id, so we can have the parent ID. + $key = (string) absint( $cart_id ); + $requirement_met = array_key_exists( $key, $requirements ); + } + + if ( $requirement_met ) { + $requirements[ $key ] = true; + } + } + + if ( 'all' === $this->get_product_condition() ) { + $is_met = ! in_array( false, $requirements, true ); + } else { + $is_met = in_array( true, $requirements, true ); + } + + 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 = $this->get_excluded_products(); + $excluded_ps = array_map( 'absint', $excluded_ps ); + asort( $excluded_ps ); + $excluded_ps = array_filter( array_values( $excluded_ps ) ); + + if ( ! empty( $excluded_ps ) ) { + // Cast the card IDs back to integers. + $cart_ids = array_unique( array_map( 'absint', $cart_ids ) ); + 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 ); + } + + /** + * Is the discount valid for the selected categories? + * + * @since 3.2.0 + * @param bool $set_error Whether an error message be set in session. + * @param false|array $cart_ids Cart item IDs or a specific array of download IDs to check. + * @return bool + */ + public function is_valid_for_categories( $set_error = true, $cart_ids = false ) { + $categories = $this->get_categories(); + // If no categories are set, then the condition is met. + if ( empty( $categories ) ) { + return true; + } + + // Assume the condition is not met. + $categories = array_map( 'intval', $categories ); + $term_condition = $this->get_term_condition(); + if ( false === $cart_ids ) { + $cart_ids = $this->get_cart_ids(); + } + + // If any of the cart items have a category that matches the discount and the condition is include, return true. + if ( empty( $term_condition ) ) { + if ( $this->item_has_terms( $cart_ids, $categories ) ) { + return true; + } + } + + if ( 'exclude' === $term_condition ) { + // Check each item in the cart to see if it has a category that matches the discount. Return true as soon as one is found. + foreach ( $cart_ids as $download_id ) { + if ( ! $this->item_has_terms( $download_id, $categories ) ) { + return true; + } + } + } + + if ( $set_error ) { + edd_set_error( 'edd-discount-error', __( 'This discount is not valid for the cart contents.', 'easy-digital-downloads' ) ); + } + + return false; + } + + /** + * Gets the categories for the discount. + * + * @since 3.2.0 + * @return array + */ + public function get_categories() { + return array_filter( (array) edd_get_adjustment_meta( $this->id, 'categories', true ) ); + } + + /** + * Gets the term condition for the discount. + * + * @since 3.2.0 + * @return string + */ + public function get_term_condition() { + return (string) edd_get_adjustment_meta( $this->id, 'term_condition', true ); + } + + /** + * Has the discount code been used. + * + * @since 2.7 + * @since 3.0 Refactored to use new query methods. + * @since 3.2 Refactored to use the order adjustments table, and always run the filter. + * + * @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 ) { + $discount_used = false; + + if ( $this->is_single_use ) { + $by_user_id = ! is_email( $user ); + $customer = new EDD_Customer( $user, $by_user_id ); + + if ( ! empty( $customer ) ) { + $order_ids = $customer->get_order_ids( edd_get_complete_order_statuses() ); + $order_adjustments = edd_get_order_adjustments( + array( + 'type' => 'discount', + 'type_id' => $this->id, + 'object_id__in' => $order_ids, + 'object_type' => 'order', + ) + ); + + $discount_used = ! empty( $order_adjustments ); + } + } + + /** + * Filters if the discount is used or not. + * + * @since 2.7 + * @since 3.2 This filter is now always run prior to returning if it has been used. Previously if the discount was found to be used, + * the method returned early and this filter would have never been run. + * + * @param bool $discount_used 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. + */ + $discount_used = apply_filters( 'edd_is_discount_used', $discount_used, $this->id, $user, $set_error ); + + if ( true === $discount_used && $set_error ) { + edd_set_error( 'edd-discount-error', __( 'This discount has already been redeemed.', 'easy-digital-downloads' ) ); + } + + return $discount_used; + } + + /** + * 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 ) { + $return = $this->is_valid_for_conditions( $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' ) ); + } + + /** + * 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 archived. + * + * @since 3.2.0 + * + * @param bool $set_error Whether an error message be set in session. + * @return bool If the discount is archived or not. + */ + public function is_archived( $set_error = true ) { + $is_archived = false; + + if ( 'archived' === $this->status ) { + $is_archived = true; + if ( $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 $is_archived; + } + + /** + * Checks if a discount code is active. + * + * @since 2.7 + * @since 3.2 Also verifies that a discount hasn't started before returning `active`. + * + * @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() && 'archived' !== $this->status ) { + + 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 ( ! $this->is_started( $set_error ) ) { + $return = false; + } 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; + } + + /** + * Gets the IDs of all of the items in the cart. + * + * @since 3.2.0 + * @return array + */ + private function get_cart_ids() { + $cart_items = edd_get_cart_contents(); + if ( empty( $cart_items ) ) { + return array(); + } + + $cart_ids = wp_list_pluck( $cart_items, 'id' ); + $cart_ids = array_filter( array_map( 'absint', $cart_ids ) ); + asort( $cart_ids ); + + return array_values( $cart_ids ); + } + + /** + * Checks if a cart item/download has any of the specified categories. + * + * @since 3.2.0 + * @param int|int[] $download_ids Download ID(s) to check. + * @param array $categories Category IDs to check. + * @return bool + */ + private function item_has_terms( $download_ids, $categories ) { + $cart_item_categories = wp_get_object_terms( $download_ids, 'download_category', array( 'fields' => 'ids' ) ); + if ( empty( $cart_item_categories ) || is_wp_error( $cart_item_categories ) ) { + return false; + } + + $cart_item_categories = array_map( 'intval', $cart_item_categories ); + if ( count( array_intersect( $cart_item_categories, $categories ) ) > 0 ) { + return true; + } + + // Check for parent categories. + foreach ( $cart_item_categories as $term_id ) { + $parent_category_ids = array_map( 'intval', get_ancestors( $term_id, 'download_category', 'taxonomy' ) ); + + if ( count( array_intersect( $parent_category_ids, $categories ) ) > 0 ) { + return true; + } + } + + return false; + } + + /** + * Checks if the discount is valid for the conditions. + * + * @since 3.3.6 + * @param string $user User info. + * @param bool $set_error Whether to set an error message. + * @return bool + */ + private function is_valid_for_conditions( $user, $set_error = true ) { + if ( $this->is_archived( $set_error ) ) { + return false; + } + if ( ! $this->is_started( $set_error ) ) { + return false; + } + if ( ! $this->is_active( true, $set_error ) ) { + return false; + } + if ( $this->is_maxed_out( $set_error ) ) { + return false; + } + if ( $this->is_used( $user, $set_error ) ) { + return false; + } + if ( ! $this->is_min_price_met( $set_error ) ) { + return false; + } + if ( ! $this->is_product_requirements_met( $set_error ) || ! count( array_filter( $this->get_product_reqs() ) ) && ! $this->is_valid_for_categories( $set_error ) ) { + return false; + } + return true; + } +} diff --git a/includes/class-edd-download.php b/includes/class-edd-download.php new file mode 100644 index 00000000000..abecad601dc --- /dev/null +++ b/includes/class-edd-download.php @@ -0,0 +1,1164 @@ +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', edd_format_amount( $this->price, true, '', 'data' ), $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 = array_filter( (array) get_post_meta( $this->ID, 'edd_variable_prices', true ) ); + foreach ( $this->prices as $key => $price ) { + $this->prices[ $key ] = wp_parse_args( + $price, + array( + 'amount' => '', + 'name' => '', + 'index' => $key, + ) + ); + } + } + } + + /** + * Override variable prices + * + * @since 2.2 + * + * @param array $prices The array of variables prices. + * @param int|string The ID of the download. + */ + return (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 ) ) { + + // Check the global limit first. The default is 0. + $limit = edd_get_option( 'file_download_limit', 0 ); + $meta = get_post_meta( $this->ID, '_edd_download_limit', true ); + + // The download specific limit will override the global limit. + if ( ! empty( $meta ) ) { + $limit = $meta; + } + + $this->file_download_limit = absint( $limit ); + } + + 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 ) ) { + + // Check the global value first. The default is `refundable`. + $refundability = edd_get_option( 'refundability', 'refundable' ); + $meta = get_post_meta( $this->ID, '_edd_refundability', true ); + + // The download specific value will override the global. + if ( ! empty( $meta ) ) { + $refundability = $meta; + } + + $this->refundability = $refundability; + } + + // This is not filtered. + return $this->refundability; + } + + /** + * 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 ) ) { + + if ( ! edd_shop_supports_buy_now() ) { + $button_behavior = 'add_to_cart'; + } else { + $button_behavior = get_post_meta( $this->ID, '_edd_button_behavior', true ); + if ( empty( $button_behavior ) || ( 'direct' === $button_behavior && ! $this->supports_buy_now() ) ) { + $button_behavior = 'add_to_cart'; + } + } + + $this->button_behavior = $button_behavior; + } + + 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' ); + } + + /** + * Determine if the download can support the Buy Now feature. + * + * @since 3.2.2 + * @param int|null $price_id The price ID to check for. + * + * @return bool True if the download can support Buy Now, false otherwise. + */ + public function supports_buy_now( $price_id = null ) { + // We have a few addons we have to check for, that would prevent Buy Now from working. + $recurring_active = function_exists( 'edd_recurring' ); + $free_downloads_active = function_exists( 'edd_free_downloads_use_modal' ); + + // If Recurring and Free Downloads are not present, we can return true. + if ( false === $recurring_active && false === $free_downloads_active ) { + return true; + } + + // Free downloads does not support Buy Now. + if ( $free_downloads_active && ! $this->has_variable_prices() ) { + $price = get_post_meta( $this->ID, 'edd_price', true ); + // If the download is free, we can return false. This check bypasses the is_free() method, to omit the filter. + if ( empty( $price ) && edd_free_downloads_use_modal( $this->ID ) ) { + return false; + } + } + + // Subscription products cannot support Buy Now. + if ( $recurring_active ) { + if ( $this->has_variable_prices() ) { + // Parse if we have a price ID passed in. + $price_id = is_numeric( $price_id ) ? intval( $price_id ) : null; + // If no Price ID was passed in, and the product has variable prices, return false if any of the prices are recurring. + if ( null === $price_id ) { + foreach ( $this->get_prices() as $key => $price ) { + if ( edd_recurring()->is_price_recurring( $this->ID, $key ) ) { + return false; + } + } + } + + $is_recurring = edd_recurring()->is_price_recurring( $this->ID, $price_id ); + } else { + $is_recurring = edd_recurring()->is_recurring( $this->ID ); + } + + if ( $is_recurring ) { + return false; + } + } + + return true; + } +} diff --git a/includes/class-edd-license-handler.php b/includes/class-edd-license-handler.php new file mode 100644 index 00000000000..037bcfc7be8 --- /dev/null +++ b/includes/class-edd-license-handler.php @@ -0,0 +1,526 @@ +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_handler->should_check_failed_request = true; + $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 ); + } + + /** + * Auto updater + * + * @access private + * @return void + */ + public function auto_updater() { + + $doing_cron = EDD\Utils\Request::is_request( '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 ( edd_is_pro() ) { + 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 + * @deprecated 3.2.1 + * @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-register-meta.php b/includes/class-edd-register-meta.php new file mode 100644 index 00000000000..e32c4d60ddc --- /dev/null +++ b/includes/class-edd-register-meta.php @@ -0,0 +1,479 @@ +hooks(); + } + + /** + * Get the one true instance of EDD_Register_Meta. + * + * @since 2.5 + * @return $instance + */ + public static 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' ) ); + } + + /** + * 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 + * @deprecated 3.2.4 + * @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 sanitized to be an int. + */ + public function intval_wrapper( $value ) { + return intval( $value ); + } + + /** + * Sanitize values that come in as arrays + * + * @since 2.5 + * @param array|string $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'] ); + $prices[ $id ]['name'] = sanitize_text_field( $price['name'] ); + + } + + 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 + */ + public 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 whitespace is 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 $updated_meta Array of all the meta values. + * @return array $new New meta value with empty keys removed + */ + private function remove_blank_rows( $updated_meta ) { + + if ( is_array( $updated_meta ) ) { + foreach ( $updated_meta as $key => $value ) { + if ( empty( $value['name'] ) && empty( $value['amount'] ) && empty( $value['file'] ) ) { + unset( $updated_meta[ $key ] ); + } + } + } + + return $updated_meta; + } +} +EDD_Register_Meta::instance(); diff --git a/includes/class-edd-roles.php b/includes/class-edd-roles.php new file mode 100755 index 00000000000..a1d15bf9818 --- /dev/null +++ b/includes/class-edd-roles.php @@ -0,0 +1,369 @@ + 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' ); + } + } + + /** + * Adds a capabiilities reset tool to the users.php page. + * + * @since 3.3.4 + * @param string $which + * @return void + */ + public function add_reset_tool( $which ) { + if ( 'top' === $which ) { + return; + } + if ( ! current_user_can( 'edit_users' ) ) { + return; + } + $url = wp_nonce_url( + add_query_arg( + 'edd-action', + 'reset_capabilities', + admin_url( 'users.php' ) + ), + 'edd-reset-capabilities' + ); + ?> +
    + +
    + add_roles(); + $this->add_caps(); + + edd_redirect( + add_query_arg( + 'edd-message', + 'capabilities_reset', + admin_url( 'users.php' ) + ) + ); + } +} 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..198fd17695d --- /dev/null +++ b/includes/class-stats.php @@ -0,0 +1,3257 @@ +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' ); + if ( ! empty( $query['fully_refunded'] ) ) { + $query['where_sql'] = "AND {$this->get_db()->edd_orders}.parent IN ( + SELECT id + FROM {$this->get_db()->edd_orders} + WHERE type = 'sale' AND status = 'refunded' + )"; + } + + 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( 'complete' ); + + // Include a query for the parent order item being refunded. + $query['where_sql'] = "AND {$this->get_db()->edd_order_items}.parent IN ( + SELECT id + FROM {$this->get_db()->edd_order_items} + WHERE status = '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(); + + $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'] ) ) { + $date_query_sql .= "{$this->query_vars['table']}.{$this->query_vars['date_query_column']} "; + $date_query_sql .= $this->get_db()->prepare( '>= %s', $this->query_vars['start'] ); + } + + // 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'] ) ) { + $date_query_sql .= $this->get_db()->prepare( "{$this->query_vars['table']}.{$this->query_vars['date_query_column']} <= %s", $this->query_vars['end'] ); + } + + $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 ) { + // Format the percentage change to two decimal places. + $formatted_percentage_change = number_format( $percentage_change, 2 ); + + // If the percentage change is negative, make it positive for display purposes. We handle the visual aspect via an icon in the UI. + $formatted_percentage_change = $formatted_percentage_change < 0 ? $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' ); + } elseif ( $relative_data['comparable'] ) { + // Determine the direction of the change. + $direction_suffix = $relative_data['reverse'] ? ' reverse' : ''; + $direction = $relative_data['percentage_change'] > 0 ? 'up' : 'down'; + $direction .= $direction_suffix; + + // Prepare the output with proper escaping and formatting. + $icon = ''; + $percentage = $relative_data['formatted_percentage_change'] . '%'; + $relative_output = $icon . ' ' . $percentage; + } else { + $relative_output = '' . esc_html__( '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/compat-functions.php b/includes/compat-functions.php new file mode 100644 index 00000000000..3d0db60249d --- /dev/null +++ b/includes/compat-functions.php @@ -0,0 +1,13 @@ +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/component-functions.php b/includes/component-functions.php new file mode 100644 index 00000000000..cf17bd96fbd --- /dev/null +++ b/includes/component-functions.php @@ -0,0 +1,347 @@ + $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, + ) ); + + edd_register_component( + 'email', + array( + 'schema' => '\\EDD\\Database\\Schemas\\Emails', + 'table' => '\\EDD\\Database\\Tables\\Emails', + 'query' => '\\EDD\\Database\\Queries\\Email', + 'object' => '\\EDD\\Emails\\Email', + 'meta' => '\\EDD\\Database\\Tables\\EmailMeta', + ) + ); + + edd_register_component( + 'log_email', + array( + 'schema' => '\\EDD\\Database\\Schemas\\LogsEmails', + 'table' => '\\EDD\\Database\\Tables\\LogsEmails', + 'query' => '\\EDD\\Database\\Queries\\LogEmail', + 'object' => '\\EDD\\Emails\\LogEmail', + 'meta' => '\\EDD\\Database\\Tables\\LogsEmailMeta', + ) + ); + + // 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..52a5decdf2c --- /dev/null +++ b/includes/country-functions.php @@ -0,0 +1,427 @@ +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 ); + + /** + * Filters the state name for a given country and state code. + * + * @param string $state_name The state name. + * @param string $state_code The ISO Code for the state. + * @param string $country_code The ISO Code for the country. (added in 3.3.0). + */ + return apply_filters( 'edd_get_state_name', $state_name, $state_code, $country_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..7195badd49e --- /dev/null +++ b/includes/customer-functions.php @@ -0,0 +1,1004 @@ +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() ) { + if ( isset( $data['email'] ) && empty( $data['email'] ) ) { + return false; + } + + $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 = edd_get_customer_email_addresses( + array( + 'email' => $value, + 'order' => 'ASC', + 'order_by' => 'id', + ) + ); + $customer = false; + foreach ( $customer_emails as $customer_email ) { + if ( ! empty( $customer_email->customer_id ) ) { + $customer = $customers->get_item_by( 'id', $customer_email->customer_id ); + if ( $customer ) { + break; + } + + edd_debug_log( sprintf( 'Invalid customer ID %d found for email address: %s', $customer_email->customer_id, $value ) ); + } + } + + if ( empty( $customer ) ) { + $customer = $customers->get_item_by( 'email', $value ); + + if ( ! empty( $customer ) ) { + $customer_has_primary = edd_count_customer_email_addresses( + array( + 'customer_id' => $customer->id, + 'type' => 'primary', + ) + ); + edd_add_customer_email_address( + array( + 'customer_id' => $customer->id, + 'email' => $value, + 'type' => $customer_has_primary ? 'secondary' : 'primary', + ) + ); + } + } + } 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. + */ + return apply_filters( 'edd_get_customer', $customer, $customers->query_vars, $customers_db ); +} + +/** + * 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..efc55bb5a43 --- /dev/null +++ b/includes/customers/class-customer-address.php @@ -0,0 +1,165 @@ + $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 ); + // Make sure we have a valid user object and that the user has an email address. + if ( $user instanceof WP_User && ! empty( $user->user_email ) ) { + 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 the user IDs for any orders that may have been placed by this customer. + $order_ids = edd_get_orders( + array( + 'customer_id' => $customer->id, + 'number' => 9999999, + 'fields' => 'id', + ) + ); + + 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..28ce154a2aa --- /dev/null +++ b/includes/database/engine/class-base.php @@ -0,0 +1,340 @@ +{$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..1cd69d2ff65 --- /dev/null +++ b/includes/database/engine/class-column.php @@ -0,0 +1,978 @@ +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, + 'compare' => false, + + // 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', + 'compare' => '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', + 'smallint', + '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..d5b2e0d2a23 --- /dev/null +++ b/includes/database/engine/class-date.php @@ -0,0 +1,1268 @@ +', + '>=', + '<', + '<=', + 'IN', + 'NOT IN', + 'BETWEEN', + 'NOT BETWEEN', + 'IS NULL', + ); + + /** + * 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->is_null_check( $queries ) ) { + $this->validate_date_values( $queries ); + } + + // Add queries to return array. + foreach ( $queries as $key => $q ) { + + if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) { // This is a first-order query. Trust the values and sanitize when building SQL. + $retval[ $key ] = $q; + } elseif ( array_key_exists( 'compare', $q ) && 'IS NULL' === $q['compare'] ) { // If this isn't a time query, but is a compare for `IS NULL` we can trust the value. + $retval[ $key ] = $q; + } else { // Any array without a time key is another query, so we recurse. + $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 whether this is a null check. + * + * This allows the `compare` of a date query to be `IS NULL`. Usually this wouldn't be something used in date columns, + * but in some tables the date is an optional value and this allows for querying for null values. + * + * @since 3.2.0 + * + * @param array $queries A date query or a date subquery. + * + * @return bool True if this is a null check. + */ + protected function is_null_check( $queries = array() ) { + return ( array_key_exists( 'compare', $queries ) && 'IS NULL' === $queries['compare'] ); + } + + /** + * 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; + + // Allow IS NULL values. + if ( array_key_exists( 'compare', $date_query ) && 'IS NULL' === $date_query['compare'] ) { + return $valid; + } + + /* + * 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 ) || $this->is_null_check( $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 .= '='; + } + + // NULL values. + if ( isset( $query['compare'] ) && 'IS NULL' === $query['compare'] ) { + $where_parts[] = "{$column} IS NULL"; + } + + // 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]; + + case 'IS NULL': + return ''; + + 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 and IS NULL. + case 'NOT EXISTS': + case 'IS NULL': + $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' => '' + ); + + /** + * Compare operators supported by this query. + * + * These are restricted to values that can be done on numeric values. + */ + protected $supported_compare_opperators = array( + '=', + '!=', + '>', + '>=', + '<', + '<=', + 'BETWEEN', + 'NOT BETWEEN' + ); + + /** + * 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. + * @type array ${column}__compare An array to compare one column against another value, including another column. + * Restircted to numeric column types and values. + * } + */ + 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 compares. + $possible_compares = $this->get_columns( array( 'compare' => true ), 'and', 'name' ); + foreach ( $possible_compares as $compare ) { + $key = "{$compare}__compare"; + $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; + /** + * Custom workaround to fix a bug in Berlin, + * which breaks counts when the fields are set. + * + * @link https://github.com/berlindb/core/issues/168 + */ + $this->query_vars['fields'] = ''; + } + + // 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 ] ) ) . "' )"; + } + } + } + + // __compare + if ( true === $column->compare ) { + $where_id = "{$column->name}__compare"; + + // Parse item for a compare clause. + if ( isset( $this->query_vars[ $where_id ] ) && is_array( $this->query_vars[ $where_id ] ) ) { + $where[ $where_id] = $this->parse_compare( $this->query_vars[ $where_id ], $column ); + } + } + + // 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'; + } + + /** + * Parses a 'compare' query variable and cast it to a valid compare operator. + * + * This is used to compare one column in a table against a value, or another column in the same table. + * Note: Currently this only supports numeric values. Added in EDD 3.2, only the adjustments max_uses and use_count are supported. + * + * Adding a compare can either be a single array or an array of arrays, with a `relation` key. There is no default relation, so it will be ignored if not set. + * + * Not yet supported is relations within relations. + * + * Example: + * Get a column with values ranging from 11 and 19 + * __compare => array( + * 'relation' => 'AND', + * array( + * 'value' => 10, + * 'compare' => '>', + * ), + * array( + * 'value' => 20, + * 'compare' => '<', + * ), + * ) + * + * Check a column is greater than another column in the same table. + * __compare => array( + * 'value' => 'another_column', + * 'compare' => '>', + * ) + */ + private function parse_compare( $compare, $column ) { + // Check if a relation key exists. Save it for the main loop. + $relation = isset( $compare['relation'] ) ? $compare['relation'] : false; + unset( $compare['relation'] ); + + $where = ''; + + // If we have multiple arrays, we need to itterate over them. + if ( isset( $compare[0] ) ) { + $where = array(); + + foreach ( $compare as $compare_array ) { + $compare_hash = md5( wp_json_encode( $compare_array ) ); + $compare_where = $this->parse_compare( $compare_array, $column ); + + if ( ! empty( $compare_where ) ) { + $where[ $compare_hash ] = $compare_where; + } + } + } + + if ( ! is_array( $where ) ) { + + // Continue if we don't have a value or an operator. + if ( ! isset( $compare['value'] ) || ! isset( $compare['compare'] ) ) { + return false; + } + + // Ensure we have a valid compare operator in the compare array key. + $operator = in_array( $compare['compare'], $this->supported_compare_opperators, true ) + ? $compare['compare'] + : false; + + $compare_with = false; + $value_type = '%d'; + // Only add the compare clause if we have a valid operator. + if ( false !== $operator ) { + $compare_with = $compare['value']; + + // If the value isn't numeric, we are comapreing with another column. + if ( ! is_numeric( $compare_with ) ) { + + // Verify this column supports doing a 'compare'. + $supports_compare = $this->get_column_field( array( 'name' => $compare_with ), 'compare', false ); + if ( false === $supports_compare ) { + return false; + } + + // Define the compare with column, including the alias. + $compare_with = "{$this->table_alias}.{$compare_with}"; + + // A column compare + $value_type = '%1s'; + } elseif ( is_float( $compare_with ) ) { + $compare_with = floatval( $compare_with ); + $value_type = '%f'; + } + + $statement = "{$this->table_alias}.{$column->name} {$operator} {$value_type}"; + $where = $this->get_db()->prepare( $statement, $compare_with ); + + // Return the where statement for an individual comparrison. + return $where; + } + } + + // If we have a relation, we need to wrap the where clauses in parenthesis. + if ( false !== $relation ) { + $where = '(' . implode( ' ' . strtoupper( $relation ) . ' ', $where ) . ')'; + } + + return $where; + } + + /** 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..06fa3f7d984 --- /dev/null +++ b/includes/database/queries/class-adjustment.php @@ -0,0 +1,209 @@ +update_empty_start_end_dates( $data ) ); + } + + /** + * Updates an adjustment in the database. + * + * @param int $item_id The ID of the adjustment to update. + * @param array $data The array of data to update in the database. + * @return int|false + */ + public function update_item( $item_id = 0, $data = array() ) { + return parent::update_item( $item_id, $this->update_empty_start_end_dates( $data ) ); + } + + /** + * Updates empty start and end dates. + * + * @param array $data The data to filter. + * @return array + */ + private function update_empty_start_end_dates( $data ) { + if ( isset( $data['start_date'] ) && empty( $data['start_date'] ) ) { + $data['start_date'] = null; + } + + if ( isset( $data['end_date'] ) && empty( $data['end_date'] ) ) { + $data['end_date'] = null; + } + + return $data; + } +} diff --git a/includes/database/queries/class-customer-address.php b/includes/database/queries/class-customer-address.php new file mode 100644 index 00000000000..ac61ed6be25 --- /dev/null +++ b/includes/database/queries/class-customer-address.php @@ -0,0 +1,169 @@ +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, + 'compare' => true, + ), + + // description. + array( + 'name' => 'description', + 'type' => 'longtext', + 'default' => '', + 'searchable' => true, + ), + + // max_uses. + array( + 'name' => 'max_uses', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'compare' => true, + ), + + // use_count. + array( + 'name' => 'use_count', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true, + 'compare' => 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..21e363174ca --- /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' => '255', + '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..fb0b438690b --- /dev/null +++ b/includes/database/schemas/class-order-items.php @@ -0,0 +1,220 @@ + '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, + 'compare' => 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..1a7d3b6b3f3 --- /dev/null +++ b/includes/database/schemas/class-orders.php @@ -0,0 +1,266 @@ + '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, + ), + + // date_actions_run. + array( + 'name' => 'date_actions_run', + '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..f7b2ed85374 --- /dev/null +++ b/includes/database/tables/class-adjustments.php @@ -0,0 +1,230 @@ + 201906031, + '202002121' => 202002121, + '202102161' => 202102161, + '202307311' => 202307311, + ); + + /** + * 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 type_status_dates (type(20), status(20), start_date, end_date), + 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" ); + } + + if ( ! $this->index_exists( 'type_status' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX type_status (type(20), status(20))" ); + } + + if ( ! $this->index_exists( 'code' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX code (code)" ); + } + + return true; + } + + /** + * Upgrade to version 202307311 + * + * - Create new `type_status_dates` index that includes the start and end dates. + * + * @since 3.2.0 + * @return bool + */ + protected function __202307311() { + if ( ! $this->index_exists( 'type_status_dates') ) { + $result = $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX type_status_dates (type(20), status(20), start_date, end_date)" ); + return $this->is_success( $result ); + } + + 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..07ea9a74d4d --- /dev/null +++ b/includes/database/tables/class-order-adjustments.php @@ -0,0 +1,202 @@ + 202002141, + '202011122' => 202011122, + '202103151' => 202103151, + '202105221' => 202105221, + '202502281' => 202502281, + ); + + /** + * 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(255) 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; + } + + /** + * Upgrade to version 202502281 + * - Change `description` column to `varchar(255)` + * + * @since 3.0 + * @return bool + */ + protected function __202502281() { + return $this->is_success( + $this->get_db()->query( + "ALTER TABLE {$this->table_name} MODIFY COLUMN description varchar(255) DEFAULT NULL" + ) + ); + } +} 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..3beb3bb94a4 --- /dev/null +++ b/includes/database/tables/class-orders.php @@ -0,0 +1,318 @@ + 201901111, + '202002141' => 202002141, + '202012041' => 202012041, + '202102161' => 202102161, + '202103261' => 202103261, + '202105221' => 202105221, + '202108041' => 202108041, + '202302241' => 202302241, + '202307111' => 202307111, + ); + + /** + * 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, + date_actions_run 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 ); + } + + protected function __202307111() { + if ( ! $this->column_exists( 'date_actions_run' ) ) { + return $this->is_success( + $this->get_db()->query( + "ALTER TABLE {$this->table_name} ADD COLUMN date_actions_run datetime default NULL AFTER date_refundable" + ) + ); + } + + return true; + } +} diff --git a/includes/date-functions.php b/includes/date-functions.php new file mode 100644 index 00000000000..f359e3c6869 --- /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(), true )->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..39ab5e86d30 --- a/includes/deprecated-functions.php +++ b/includes/deprecated-functions.php @@ -4,36 +4,2558 @@ * * 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\Emails\Registry; +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\Sessions\Handler::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; + + /* translators: 1: Old status, 2: New status */ + $status_change = sprintf( __( 'Status changed from %1$s to %2$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. 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. tag, 3. tag, 4. 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 + * + * This is so that payments can be queried by their totals * - * Returns the total number of payments recorded. + * Prevsiouly hooked into: edd_upgrade_payments * - * @access public - * @since 1.0 - * @return integer + * @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' ); + } +} + +/** + * 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(); +} + +/** + * Schedules the one time event via WP_Cron to fire after purchase actions. + * + * Is run on the edd_complete_purchase action. + * + * @since 2.8 + * @since 3.2.0 - Updated to call the DeferredActions class. Throwing an edd_debug_log entry instead of full deprecation. + * + * @deprecated 3.2.0 Moved to EDD\Orders\DeferredActions::schedule_deferred_actions(). This should not be used by anyone. + * + * @param $payment_id + */ +function edd_schedule_after_payment_action( $payment_id ) { + edd_debug_log( 'Calling edd_scheduled_after_payment_action directly has been deprecated in EDD 3.2. Please use the EDD\Orders\DeferredActions class instead.' ); + + /** + * @todo offiically throw a deprecated function notice. + */ + + EDD\Orders\DeferredActions::schedule_deferred_actions( $payment_id ); +} + +/** + * Executes the one time event used for after purchase actions. + * + * This function should have never been called from anywhere but outselves and on our cron. + * + * @since 2.8 + * @since 3.1.0.4 This also verifies that all order items have the synced status as the order. + * + * @deprecated 3.2.0 - Moved to \EDD\Orders\DeferredActions\run_deferred_actions(). This should not be used by anyone. + * + * @param $payment_id + * @param $force + */ +function edd_process_after_payment_actions( $payment_id = 0, $force = false ) { + _edd_deprecated_function( __FUNCTION__, '3.2.0', '\EDD\Orders\DeferredActions\run_deferred_actions' ); +} + +/* + * Registers custom post statuses which are used by the Payments and Discount + * Codes. + * + * @since 1.0.9.1 + * @deprecated 3.2.0 + */ +function edd_register_post_type_statuses() { + _edd_deprecated_function( __FUNCTION__, '3.2.0' ); +} + +/** + * Triggers Purchase Receipt to be sent after the payment status is updated. + * + * This is a deprecated function but still exists, so that we can detect if a plugin has removed the action. + * + * @since 1.0.8.4 + * @since 2.8 - Add parameters for EDD_Payment and EDD_Customer object. + * + * @deprecated 3.2.0 - Use EDD\Emails\Types\OrderReceipt instead. + * + * @param int $payment_id Payment ID. + * @param EDD_Payment $payment Payment object for payment ID. + * @param EDD_Customer $customer Customer object for associated payment. + * @return void + */ +function edd_trigger_purchase_receipt( $payment_id = 0, $payment = null, $customer = null ) { + + if ( 'edd_complete_purchase' !== current_action() ) { + _edd_deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Types\OrderReceipt' ); + } + + /** + * In EDD 3.2.0 we will be using the EDD\Emails\Triggers\send_purchase_receipt() function to send the email. + * + * Previously if you wanted to disable this email from sending you would just unhook this action. In order to still support this, + * we'll set a meta here, and check for it when it comes time to send the email. + * + * Developers: If you want to disable sending the purchase receipt email you should use the filter `edd_disable_order_receipt` filter. + */ + edd_add_order_meta( $payment_id, '_edd_should_send_order_receipt', '1' ); + edd_debug_log( 'Adding meta for sending the order_receipt for order #' . $payment_id ); + + // Trigger this to setup the edd_admin_sale_notice hook. + do_action( 'edd_admin_sale_notice', $payment_id, array() ); +} +add_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999, 3 ); + +/** + * Email the download link(s) and payment confirmation to the buyer in a + * customizable Purchase Receipt + * + * @since 1.0 + * @since 2.8 - Add parameters for EDD_Payment and EDD_Customer object. + * @since 3.2.0 - Removed most logic and using the EDD\Emails\Types\OrderReceipt class to send the email. + * + * @deprecated 3.2.0 Use EDD\Emails\Types\OrderReceipt instead. + * + * @param int $payment_id Payment ID + * @param bool $admin_notice Whether to send the admin email notification or not (default: true) + * @param EDD_Payment $payment Payment object for payment ID. + * @param EDD_Customer $customer Customer object for associated payment. + * @return bool Whether the email was sent successfully. + */ +function edd_email_purchase_receipt( $payment_id, $admin_notice = true, $to_email = '', $payment = null, $customer = null ) { + _edd_deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Types\OrderReceipt' ); + + if ( ! empty( $payment_id ) ) { + $order = edd_get_order( $payment_id ); + } + + $order_receipt = EDD\Emails\Registry::get( 'order_receipt', array( $order ) ); + $order_receipt->send_to = $to_email; + + $sent = $order_receipt->send(); + + return $sent; +} + +/** + * Resend the Email Purchase Receipt. (This can be done from the Payment History page) + * + * @since 1.0 + * + * @deprecated 3.2.0 - Handeled with EDD\Emails\Triggers\resend_purchase_receipt instead. + * + * @param array $data Payment Data + * @return void + */ +function edd_resend_purchase_receipt( $data ) { + _edd_deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Triggers\resend_purchase_receipt' ); + + /** + * In EDD 3.2.0 we will be using the EDD\Emails\Triggers\send_purchase_receipt() function to send the email. + * + * Previously if you wanted to disable this email from sending you would just unhook this action. In order to still support this, + * we'll set a meta here, and check for it when it comes time to send the email. + * + * Developers: If you want to disable sending the purchase receipt email you should use the filter `edd_disable_order_receipt` filter. + */ + edd_add_order_meta( $data['purchase_id'], '_edd_should_send_order_receipt', '1' ); +} + +/** + * Trigger the sending of a Test Email + * + * @since 1.5 + * + * @deprecated 3.2.0 - Handeled with EDD\Emails\Triggers\send_test_email instead. + * + * @param array $data Parameters sent from Settings page + * @return void + */ +function edd_send_test_email( $data ) { + _edd_deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Triggers\send_test_email' ); +} + +/** + * Email the download link(s) and payment confirmation to the admin accounts for testing. + * + * @since 1.5 + * + * @since 3.2.0 - Removed all logic as we are now using the EDD\Emails\Triggers and EDD\Emails\Types\OrderReceipt class to send the email. + * + * @return void + */ +function edd_email_test_purchase_receipt() { + _edd_deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Triggers' ); +} + +/** + * This is a deprecated function now, but still exists so we can know if users are removeing it's action, + * to know if we are supposed to send the admin notices or not. + * + * @since 1.4.2 + * @since 3.2.0 - Removed all logic as we are now using the EDD\Emails\Types\AdminOrderNotice class to send the email. + * + * @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() ) { + /** + * In EDD 3.2.0 we will be using the EDD\Emails\Triggers\edd_email_sent_order_receipt() function to send the email. + * + * Previously if you wanted to disable this email from sending you would just unhook this action. In order to still support this, + * we'll set a meta here, and check for it when it comes time to send the email. + * + * Developers: If you want to disable sending the admin order notice email you should use the filter `edd_disable_admin_order_notice` filter. + */ + edd_add_order_meta( $payment_id, '_edd_should_send_admin_order_notice', '1' ); + edd_debug_log( 'Adding meta for sending the admin_order_notice for order #' . $payment_id ); +} +add_action( 'edd_admin_sale_notice', 'edd_admin_email_notice', 10, 2 ); + +/** + * Displays the email preview + * + * @since 2.1 + * + * @deprecated 3.2.0 This is now handeled by EDD\Emails\Triggers\preview_email() + * + * @return void + */ +function edd_display_email_template_preview() { + _edd_deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Triggers\preview_email()' ); +} + +/** + * Get sale notification email text + * + * Returns the stored email text if available, the standard email text if not + * + * @since 1.7 + * @since 3.2.0 - Uses the EDD\Emails\Types\AdminOrderNotice class. + * + * @deprecated 3.2.0 + * + * @return string $message + */ +function edd_get_default_sale_notification_email() { + $admin_order_notice = new \EDD\Emails\Templates\AdminOrderNotice(); + + return $admin_order_notice->get_default( 'content' ); +} + +/** + * Add rating links to the admin dashboard + * + * @since 1.8.5 + * @deprecated 3.2.4 + * @global string $typenow + * @param string $footer_text The existing footer text. + * @return string + */ +function edd_admin_rate_us( $footer_text = '' ) { + _edd_deprecated_function( __FUNCTION__, '3.2.4' ); + + return $footer_text; +} + +/** + * Email Template Body + * + * @since 1.0.8.2 + * + * @deprecated 3.2.0 + * + * @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() ) { + _deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Types\OrderReceipt' ); + + $order = edd_get_order( $payment_id ); + $order_receipt = EDD\Emails\Registry::get( 'order_receipt', array( $order ) ); + + return $order_receipt->get_raw_body_content(); +} + +/** + * Sale Notification Template Body + * + * @since 1.7 + * + * @deprecated 3.2.0 + * + * @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() ) { + _edd_deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Types\AdminOrderNotice' ); + + $order = edd_get_order( $payment_id ); + $admin_order_notice = EDD\Emails\Registry::get( 'admin_order_notice', array( $order ) ); + + return $admin_order_notice->get_raw_body_content(); +} + +/** + * Delete Saved Carts after one week + * + * This function is only intended to be used by WordPress cron. + * + * @since 1.8 + * @deprecated 3.3.0 + * + * @global $wpdb + * @return void + */ +function edd_delete_saved_carts() { + _edd_deprecated_function( __FUNCTION__, '3.3.0' ); +} + +/** + * Updates week-old+ 'pending' orders to 'abandoned' + * + * This function is only intended to be used by WordPress cron. + * + * @since 1.6 + * @deprecated 3.3.0 + * @return void +*/ +function edd_mark_abandoned_orders() { + _edd_deprecated_function( __FUNCTION__, '3.3.0' ); +} + +/** + * Sends the new user notification email when a user registers during checkout + * + * @since 1.8.8 + * @param int $user_id + * @param array $user_data + * + * @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: %s: The 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: %s: The customer's 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 ); + + // 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 ); + /* translators: %s: The customer's username */ + $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 ); +} + +/** + * Drop our custom tables when a mu site is deleted + * + * @deprecated 3.0 Handled by WP_DB_Table + * @since 2.5 + * @param array $tables The tables to drop + * @param int $blog_id The Blog ID being deleted + * @return array The tables to drop + */ +function edd_wpmu_drop_tables( $tables, $blog_id ) { + + switch_to_blog( $blog_id ); + $customers_db = new EDD_DB_Customers(); + $customer_meta_db = new EDD_DB_Customer_Meta(); + if ( $customers_db->installed() ) { + $tables[] = $customers_db->table_name; + $tables[] = $customer_meta_db->table_name; } - return $count; + restore_current_blog(); + + return $tables; } 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/deprecated/classes.php b/includes/deprecated/classes.php new file mode 100644 index 00000000000..16dda47810e --- /dev/null +++ b/includes/deprecated/classes.php @@ -0,0 +1,159 @@ +html. + * + * @deprecated 3.2.8 + */ +class_alias( \EDD\HTML\Elements::class, 'EDD_HTML_Elements' ); + +/** + * Legacy `EDD_Logging` class was refactored and moved to the new `EDD\Logging` class. + * This alias is a safeguard to those developers who use our internal class EDD_Logging, + * which we deleted. + * + * @deprecated 3.2.10 + */ +class_alias( \EDD\Logging::class, 'EDD_Logging' ); + +/** + * Some legacy classes have been completely deprecated and emptied . + * This alias is a safeguard to those developers who use our internal classes, + * which we deleted . + * + * @deprecated 3.2.10 + */ +class_alias( \EDD\Deprecated\EmptyClass::class, 'EDD_DB_Customers' ); +class_alias( \EDD\Deprecated\EmptyClass::class, 'EDD_DB_Customer_Meta' ); + +/** + * Legacy 'EDD_Email_Summaries' class that was moved to `EDD\Cron\Events\EmailSummaries` class. + * This alias is a safeguard to those developers and extensions that use our internal class EDD_Email_Summaries. + * + * @deprecated 3.3.0 + */ +class_alias( \EDD\Cron\Components\EmailSummaries::class, 'EDD_Email_Summaries' ); + +/** + * Legacy EDD\Admin\PassHandler\Cron class that was moved to `EDD\Cron\Components\Passes` class. + * + * @deprecated 3.3.0 + */ +class_alias( \EDD\Cron\Components\Passes::class, 'EDD\Admin\PassHandler\Cron' ); + +/** + * Legacy `EDD_Email_Template_Tags` class was refactored and moved to the new `EDD\Emails\Tags\Handler` class. + * This alias is a safeguard to those developers who use our internal class EDD_Email_Template_Tags, + * which we deleted. + * + * @since 3.3.0 + */ +class_alias( \EDD\Emails\Tags\Handler::class, 'EDD_Email_Template_Tags' ); + +/** + * Legacy `EDD_Session` class was refactored and moved to the new `EDD\Sessions\Handler` class. + * This alias is a safeguard to those developers who use our internal class EDD_Session, + * which we deleted. + * + * @deprecated 3.3.0 + */ +class_alias( \EDD\Sessions\Handler::class, 'EDD_Session' ); + +/** + * Class alias for the `EDD\Emails\Templates\Previews\Data` class. + */ +class_alias( \EDD\Emails\Templates\Previews\Data::class, '\\EDD\\Emails\\Templates\\PreviewData' ); + +/** + * Class Alias for the `EDD\Orders\Refund_Validator` class. + * + * @deprecated 3.3.0 + */ +class_alias( \EDD\Orders\Refunds\Validator::class, 'EDD\Orders\Refund_Validator' ); + +/** + * Class Alias for the `EDD\RequirementsCheck` class. + * + * @deprecated 3.3.3 + */ +class_alias( \EDD\RequirementsCheck::class, 'EDD_Requirements_Check' ); + +/** + * Class Alias for the `EDD\Fees\Handler` class. + * + * @deprecated 3.3.7 + */ +class_alias( \EDD\Fees\Handler::class, 'EDD_Fees' ); + +/** + * Fully Deprecated Classes + * + * These classes are fully deprecated and are no longer used internally. There are no aliases for them, and their original class + * files have been removed. This is a safeguard to those developers who use our internal classes directly. + * + * When fully deprecating a class, you must wrap it in a `class_exists` check to ensure that we do not throw a fatal error if the + * original class file was not deleted by the update/installation process. While this is an edge case it is possible for it to happen. + * + * Your stubbed version of the class should contain a constructor that throws a deprecated notice, + * and any methods that are publicly available. + */ + +if ( ! class_exists( 'EDD_Cron' ) ) : + /** + * Legacy 'EDD_Cron' class that is deprecated in favor of new Cron Loading system. + * + * Since the EDD_Cron class was used internally only with no real function outside of our own events, replacing it entirely should + * not pose an issue. + * + * @deprecated 3.3.0 This class was converted to the new namespaced Cron Loader. + */ + class EDD_Cron { + public function __construct() { // phpcs:ignore. + _edd_deprecated_class( + __CLASS__, + '3.3.0', + 'Cron management has been refactored and is located in src/Cron/Loader.php' + ); + } + public function add_schedules( $schedules = array() ) { return $schedules; } // phpcs:ignore. + public function schedule_events() {} // phpcs:ignore. + } +endif; diff --git a/includes/discount-functions.php b/includes/discount-functions.php index 9a3f933afa1..1d4100bb32b 100755 --- a/includes/discount-functions.php +++ b/includes/discount-functions.php @@ -2,523 +2,1548 @@ /** * 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; // @codeCoverageIgnore /** - * 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; + $categories = ! empty( $data['categories'] ) ? array_map( 'intval', $data['categories'] ) : null; + $term_condition = ! empty( $data['term_condition'] ) ? $data['term_condition'] : null; + $pre_convert_args = $data; + unset( $data['product_reqs'], $data['excluded_products'], $data['product_condition'], $data['categories'], $data['term_condition'] ); + + if ( ! empty( $data['expiration'] ) ) { + $data['end_date'] = $data['expiration']; + } -function edd_get_discounts() { - $discounts = get_option('edd_discounts'); - if(false === $discounts) { - add_option('edd_discounts'); + if ( ! empty( $data['start'] ) ) { + $data['start_date'] = $data['start']; + } + + // Always unset the old keys, even if they were empty. + unset( $data['expiration'], $data['start'] ); + + // 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 ( ! empty( $categories ) ) { + edd_add_adjustment_meta( $discount_id, 'categories', $categories ); + if ( ! empty( $term_condition ) ) { + edd_add_adjustment_meta( $discount_id, 'term_condition', $term_condition ); + } + } + + // If the end date has passed, mark the discount as expired. + edd_is_discount_expired( $discount_id ); } - if($discounts) - return $discounts; - return false; -} + /** + * 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'] ); + } + + if ( is_array( $data['product_reqs'] ) ) { + edd_delete_adjustment_meta( $discount_id, 'product_requirement' ); -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]; + 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 ); + } + + if ( isset( $data['categories'] ) ) { + $categories = ! empty( $data['categories'] ) ? array_map( 'intval', $data['categories'] ) : false; + if ( ! empty( $categories ) ) { + edd_update_adjustment_meta( $discount_id, 'categories', $categories ); + if ( ! empty( $data['term_condition'] ) ) { + edd_update_adjustment_meta( $discount_id, 'term_condition', sanitize_text_field( $data['term_condition'] ) ); + } else { + edd_delete_adjustment_meta( $discount_id, 'term_condition' ); + } + } else { + edd_delete_adjustment_meta( $discount_id, 'categories' ); + edd_delete_adjustment_meta( $discount_id, 'term_condition' ); + } + unset( $data['categories'] ); + unset( $data['term_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() ) { + // By default we avoid archived discounts. + $default_args = array( + 'number' => 30, + 'status__not_in' => array( 'archived' ), + ); + // If any of the passed arguments include a status query, remove our default status__not_in. + if ( isset( $args['status'] ) || isset( $args['status__not_in'] ) || isset( $args['status__in'] ) ) { + unset( $default_args['status__not_in'] ); + } + + // If there is a search supplied, clear out any status checks. + if ( isset( $args['search'] ) ) { + unset( $default_args['status__not_in'] ); + unset( $default_args['status__in'] ); + unset( $default_args['status'] ); + } + + // Parse arguments. + $r = wp_parse_args( $args, $default_args ); + + // 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() ) { + + // Parse args. + $r = wp_parse_args( + $args, + array( + 'count' => true, + ) + ); -function edd_store_discount($discount_details, $id = null) { - if(edd_discount_exists($id) && !is_null($id)) { + // Query for count(s). + $discounts = new EDD\Compat\Discount_Query( $r ); - // 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); + // Return count(s). + return absint( $discounts->found_items ); +} - do_action( 'edd_post_update_discount', $discount_details, $id ); - - // discount code updated - return true; - - } else { - // add the discount - $discounts = edd_get_discounts(); +/** + * Query for and return array of discount counts, keyed by status. + * + * @since 3.0 + * + * @return array + */ +function edd_get_discount_counts( $args = array() ) { + + // Parse arguments. + $r = wp_parse_args( + $args, + array( + 'count' => true, + 'groupby' => 'status', + ) + ); - if(!$discounts) $discounts = array(); - - $discounts[] = $discount_details; - - apply_filters( 'edd_insert_discount', $discount_details ); + // Query for count. + $counts = new EDD\Compat\Discount_Query( $r ); - update_option('edd_discounts', $discounts); - - do_action( 'edd_post_insert_discount', $discount_details ); + // Format & return. + return edd_format_counts( $counts, $r['groupby'] ); +} - // discount code created - return true; - } - - // something went wrong - return false; +/** + * 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() { + + /** + * Get discounts that are not expired, inactive, or archived, but also that + * either have no start_date or have a start_date that is in the past, and also have + * no end_date or have an end_date that is in the future. + * + * Searching by date is done with WP_Date_Query. + */ + $discount_arguments = array( + 'number' => 1, + 'type' => 'discount', + 'status__not_in' => array( 'expired', 'inactive', 'archived' ), + 'max_uses__compare' => array( + 'relation' => 'OR', + array( + 'value' => 'use_count', + 'compare' => '>', + ), + array( + 'value' => 0, + 'compare' => '=', + ), + ), + 'date_query' => array( + 'relation' => 'AND', + array( + 'relation' => 'OR', + array( + 'column' => 'start_date', + 'before' => 'now', + 'inclusive' => true, + ), + array( + 'column' => 'start_date', + 'compare' => 'IS NULL', + ), + ), + array( + 'relation' => 'OR', + array( + 'column' => 'end_date', + 'after' => 'now', + 'inclusive' => true, + ), + array( + 'column' => 'end_date', + 'compare' => 'IS NULL', + ), + ), + ), + ); + + return ! empty( edd_get_adjustments( $discount_arguments ) ); +} /** - * 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( + /* translators: 1: Function name, 2: Version number, 3: Replacement function name */ + __( '%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(); + // 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 ) ); + } - do_action( 'edd_pre_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($discounts[$discount_id]); - - do_action( 'edd_post_delete_discount', $discount_id ); + // Always unset the old keys, even if they were empty. + unset( $details['start'], $details['expiration'] ); + + /** + * 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 ); + } - update_option('edd_discounts', $discounts); + edd_update_discount( $discount_id, $details ); + return $discount_id; } - +/** + * 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 -*/ - -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 ); + * @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' ); } +/** + * 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' ); +} /** - * Is Discount Expired + * Retrieve the maximum uses that a certain discount code. * - * 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 -*/ - -function edd_is_discount_expired( $code_id = null ) { - - $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 ); + * @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' ); } +/** + * Retrieve number of times a discount has been used. + * + * @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 $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' ); +} /** - * Is Discount Started + * Retrieve the minimum purchase amount for a discount. * - * Checks whether a discount code is available yet (start date). + * @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 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' ) ); +} -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 ); +/** + * 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 Maxed Out + * Retrieve the products the discount cannot be applied to. * - * Checks to see if a discount has uses left. + * @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_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->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 Cart Minimum Met + * Retrieve the product condition. * - * Checks to see if the minimum purchase amount has been met + * @since 1.5 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field() * - * @access public - * @since 1.1.7 - * @return void -*/ + * @param int $discount_id Discount ID. + * + * @return string Product condition. + */ +function edd_get_discount_product_condition( $discount_id = 0 ) { + $discount = edd_get_discount( $discount_id ); -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(); + return $discount instanceof EDD_Discount ? $discount->product_condition : ''; +} - if( (float)$cart_amount >= (float)$min ) { - // minimum has been met - $return = true; - } - } - return apply_filters( 'edd_is_discount_min_met', $return, $code_id ); +/** + * 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' ); +} + +/** + * Checks whether a discount code is expired. + * + * @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() + * + * @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; +} + +/** + * 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; +} + +/** + * 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 Discount Used + * 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' ); +} + +/** + * 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 = is_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 ) ) { + // We found a discount code, so check it's validity. + return $discount->is_valid( $user, $set_error ); + } - $return = false; - $query_args = array( - 'post_type' => 'edd_payment', - 'meta_query' => array( - array( - 'key' => '_edd_payment_user_email', - 'value' => $email, - 'compare' => '=' - ) - ) - ); - $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; - } - } + if ( true === $set_error ) { + // We didn't find a discount, so set the default 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 apply_filters( 'edd_is_discount_used', $return, $code, $email ); + + return false; } +/** + * 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; +} /** - * Is Discount Valid + * Get Discounted Amount. * - * 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 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() * - * @access public - * @since 1.0 - * @return void -*/ - -function edd_is_discount_valid( $code = '', $email = '') { - - $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; + * @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 + * @since 3.2.0 updated to use \EDD\Discounts\ItemAmount. + * + * @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 ) { + + $item_amount = new EDD\Discounts\ItemAmount( $item, $items, $discounts, $item_unit_price ); + + return $item_amount->get_discount_amount(); +} + +/** Cart **********************************************************************/ + +/** + * Set the active discount for the shopping cart + * + * @since 1.4.1 + * @param string $code Discount code + * @return string[] All currently active discounts + */ +function edd_set_cart_discount( $code = '' ) { + + // Get all active cart discounts. + if ( edd_multiple_discounts_allowed() ) { + $discounts = edd_get_cart_discounts(); + + // Only one discount allowed per purchase, so override any existing. + } else { + $discounts = false; + } + + if ( $discounts ) { + $key = array_search( strtolower( $code ), array_map( 'strtolower', $discounts ), true ); + + // Can't set the same discount more than once. + if ( false !== $key ) { + unset( $discounts[ $key ] ); } + $discounts[] = $code; + } else { + $discounts = array(); + $discounts[] = $code; } - return apply_filters( 'edd_is_discount_valid', $return, $discount_id, $code, $email ); + + 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, true ); + + if ( false !== $key ) { + unset( $discounts[ $key ] ); + } + + $discounts = implode( '|', array_values( $discounts ) ); + // Update the active discounts. + EDD()->session->set( 'cart_discounts', $discounts ); + } + + 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(); +} + +/** + * Retrieve the currently applied discount. + * + * @since 1.4.1 + * @return array $discounts The active discount codes. + */ +function edd_get_cart_discounts() { + return EDD()->cart->get_discounts(); +} /** - * Get Discount By Code + * Check if the cart has any active discounts applied to it * - * Retrieves a discount code ID from the code. + * @since 1.4.1 + * @return bool + */ +function edd_cart_has_discounts() { + return EDD()->cart->has_discounts(); +} + +/** + * Retrieves the total discounted amount on the cart * - * @access public - * @since 1.0 - * @param $code string The discount code to retrieve an ID for - * @return void -*/ + * @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 ); +} -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; +/** + * 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 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 ); } - return $code_id; } +/** + * Processes a remove discount from cart request + * + * @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' ); /** - * Get Discounted Amount + * Checks whether discounts are still valid when removing items from the cart * - * Gets the discounted price. + * If a discount requires a certain product, and that product is no longer in + * the cart, the discount is removed. * - * @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 -*/ + * @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; } - $discounts[$discount_id]['uses'] = $uses; - return update_option('edd_discounts', $discounts); + + // Array stops the bulk delete of discount codes from storing as a preset_discount. + if ( empty( $_REQUEST['discount'] ) || is_array( $_REQUEST['discount'] ) ) { + return; + } + + $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; } -} \ No newline at end of file + + $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(); + $categories = $discount->get_categories(); + + // Return true if there are no requirements/excluded products set. + if ( empty( $product_requirements ) && empty( $excluded_products ) && empty( $categories ) ) { + return true; + } + + // Use the discount product requirement check for product requirements and exclusions. + $is_valid = $discount->is_product_requirements_met( false, $download_ids ); + + // If the discount is still valid, check the categories. + if ( $is_valid && ! empty( $categories ) ) { + $is_valid = $discount->is_valid_for_categories( false, $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..34354935590 100755 --- a/includes/download-functions.php +++ b/includes/download-functions.php @@ -2,601 +2,1617 @@ /** * 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_format_amount( edd_sanitize_amount( $amount ), true, '', 'data' ), $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( + '' => __( 'Single Product', 'easy-digital-downloads' ), + 'bundle' => __( 'Bundle', 'easy-digital-downloads' ), + 'service' => __( 'Service', '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 + * Recalculates both the net and gross sales and earnings for a download. * - * Retrieves a download post object by ID or slug. - * - * @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 ) { + + // Bail if no download ID or order ID was passed. + if ( empty( $download_id ) || empty( $order_id ) ) { + return false; + } -function edd_get_variable_prices( $download_id ) { - return get_post_meta($download_id, 'edd_variable_prices', true); + // 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 ) { -function edd_get_download_earnings_stats($download_id) { + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } - // 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; - -} + $download = edd_get_download( $download_id ); + 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. + * + * @param int $download_id Download ID. + * @param int $order_id Order ID. * - * @access public - * @since 1.0 - * @return integer + * @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 ); } +/** + * Retrieve the price option that has access to the specified file. + * + * @since 1.0.9 + * + * @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 = '' ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + + return $download + ? $download->get_file_price_condition( $file_key ) + : false; +} /** - * Record Sale In Log + * Constructs a secure file download url for a specific file. * - * Stores log information for a download sale. + * @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_sale_in_log($download_id, $payment_id, $user_info, $date) { - $log = get_post_meta($download_id, '_edd_sales_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( - 'payment_id' => $payment_id, - 'user_info' => $user_info, - '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_sales_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', + ) + ); +} /** - * Record Download In Log + * Get product notes. * - * Stores a log entry for a file 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 ) { -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(); + // Bail if download ID was not passed. + if ( empty( $download_id ) ) { + return false; } - $log_entry = array( - 'file_id' => $file_id, - 'user_info' => $user_info, - 'ip' => $ip, - 'date' => $date - ); - $log[] = $log_entry; - - update_post_meta($download_id, '_edd_file_download_log', $log); -} + $download = edd_get_download( $download_id ); + + return $download + ? $download->notes + : false; +} /** - * Increase Purchase Count + * Retrieves a download SKU by ID. * - * Increases the sale count of a download. + * @since 1.6 * - * @access public - * @since 1.0 - * @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 ) { -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; + // Bail if download ID was not passed. + if ( empty( $download_id ) ) { + return false; + } - return false; + $download = edd_get_download( $download_id ); + + return $download + ? $download->sku + : false; } /** - * Decrease Purchase Count + * Retrieve the download button behavior: either add to cart or direct. * - * Decreases the sale count of a download. Primarily for when a purchase is refunded. + * @since 1.7 * - * @access public - * @since 1.0.8.1 - * @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_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->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; +} /** - * Increase Earnings + * Get the file download method. * - * Increases the total earnings of a download. + * @since 1.6 * - * @access public - * @since 1.0 - * @return void -*/ + * @return string File download method. + */ +function edd_get_file_download_method() { + $method = edd_get_option( 'download_method', 'direct' ); -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; + 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 ); +} /** - * Decrease Earnings + * Returns random downloads. * - * Decreases the total earnings of a download. Primarily for when a purchase is refunded. + * @since 1.7 * - * @access public - * @since 1.0.8.1 - * @return void -*/ + * @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_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; -} + if ( $post_ids ) { + $args['fields'] = 'ids'; + } + $args = apply_filters( 'edd_get_random_downloads', $args ); + + return get_posts( $args ); +} /** - * Gets all download files for a product + * Generates a token for a given URL. * - * Can retrieve files specific to price ID + * 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. * - * @access public - * @since 1.0 - * @return array -*/ + * 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_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; - } - } + /* + * 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 ); } - } else { - $files = $download_files; } } - return $files; + /* + * 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; } /** - * Gets the Price ID that can download a file + * Generate a token for a URL and match it against the existing token to make + * sure the URL hasn't been tampered with. * - * @access public - * @since 1.0.9 - * @return string - the price ID if restricted, "all" otherwise -*/ + * @since 2.3 + * + * @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_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; - -} + // These are the only URL parameters that are allowed to affect the token validation. + $allowed_args = edd_get_url_token_parameters(); + + // 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 ); +} /** - * Get Download File Url + * Allows parsing of the values saved by the product drop down. * - * Constructs the file download url for a specific file. + * @since 2.6.9 * - * @access public - * @since 1.0 - * @return string -*/ + * @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(); -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 ) ) - ); + if ( is_array( $values ) ) { + foreach ( $values as $value ) { + $parsed_values[] = edd_parse_product_dropdown_value( $value ); + } + } else { + $parsed_values[] = edd_parse_product_dropdown_value( $values ); + } - $params = apply_filters('edd_download_file_url_args', $params); - - $download_url = add_query_arg($params, home_url()); - - return $download_url; + return $parsed_values; } - /** - * Verify Download Link + * Given a value from the product dropdown array, parse its parts. * - * 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 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 = absint( $parts[0] ); + $price_id = isset( $parts[1] ) + ? (int) $parts[1] + : null; + + return array( + 'download_id' => $download_id, + 'price_id' => $price_id, ); - - $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($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 - } - } - } - } - } - // payment not verified - return false; } /** - * 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..e33997d04d3 --- /dev/null +++ b/includes/downloads/recalculations.php @@ -0,0 +1,87 @@ +recalculate_order_item( $order_item_id, $data, $previous_order_item ); +} + +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 + * @since 3.2.0 updated to use the Recalculations class. + * @param int $order_adjustment_id The order adjustment ID. + * @param array $data The array of data for the new/updated order adjustment. + * @param bool|stdClass $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 ) { + $recalculations = new EDD\Downloads\Recalculations(); + $recalculations->recalculate_order_adjustment( $order_adjustment_id, $data, $previous_order_adjustment ); +} 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/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..314e7962dda --- /dev/null +++ b/includes/emails/email-summary/class-edd-email-summary-blurb.php @@ -0,0 +1,299 @@ +environment_checker = new EnvironmentChecker(); + } + + /** + * Fetch all blurbs from remote endpoint. + * + * @since 3.1 + * + * @return array + */ + public function fetch_blurbs() { + $existing_blurbs = get_option( 'edd_email_summary_blurbs', false ); + if ( false !== $existing_blurbs ) { + return $existing_blurbs; + } + + $blurbs = array(); + $request = new \EDD\Utils\RemoteRequest( self::BLURBS_ENDPOINT_URL ); + + // @todo - Detect first response code, before redirect! + if ( ! is_wp_error( $request ) && 200 === $request->code ) { + $blurbs = json_decode( $request->body, true ); + update_option( 'edd_email_summary_blurbs', $blurbs, false ); + } + + 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.php b/includes/emails/email-summary/class-edd-email-summary.php new file mode 100644 index 00000000000..4e34cfef400 --- /dev/null +++ b/includes/emails/email-summary/class-edd-email-summary.php @@ -0,0 +1,429 @@ +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 ); + } + + /** + * 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; + } + + /** + * 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() { + if ( ! is_null( $this->date_ranges ) ) { + return $this->date_ranges; + } + + $range = 'last_week'; + $previous_range = 'previous_period'; + if ( 'monthly' === $this->email_options['email_summary_frequency'] ) { + $range = 'last_month'; + $previous_range = 'previous_month'; + } + + require_once EDD_PLUGIN_DIR . 'includes/reports/reports-functions.php'; + $parsed_dates = \EDD\Reports\parse_dates_for_range( $range ); + $parsed_relative_dates = \EDD\Reports\parse_relative_dates_for_range( $range, $previous_range ); + + $this->date_ranges = array( + 'start_date' => $parsed_dates['start'], + 'end_date' => $parsed_dates['end'], + 'relative_start_date' => $parsed_relative_dates['start'], + 'relative_end_date' => $parsed_relative_dates['end'], + ); + + return $this->date_ranges; + } + + /** + * Get report start date. + * + * @since 3.1 + * @deprecated 3.3.7 + * @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() { + $ranges = $this->get_report_date_range(); + + return array( + 'start_date' => $ranges['start_date'], + 'relative_start_date' => $ranges['relative_start_date'], + ); + } + + /** + * Get report end date. + * + * @since 3.1 + * @deprecated 3.3.7 + * @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() { + + $ranges = $this->get_report_date_range(); + + return array( + 'end_date' => $ranges['end_date'], + 'relative_end_date' => $ranges['relative_end_date'], + ); + } +} 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..fcb06e9c4f4 --- /dev/null +++ b/includes/emails/email-summary/edd-email-summary-template.php @@ -0,0 +1,61 @@ + + + + <?php echo esc_html( $this->get_email_subject() ); ?> + + + + + + + + + + + + + + + + +

    + + + + + + + + + diff --git a/includes/emails/email-summary/template-parts/data-listing.php b/includes/emails/email-summary/template-parts/data-listing.php new file mode 100644 index 00000000000..f4ff195f77b --- /dev/null +++ b/includes/emails/email-summary/template-parts/data-listing.php @@ -0,0 +1,108 @@ + + + + + + + +
    + + + + +
    +

    + # +

    +

    + +

    +

    + +

    +

    + build_relative_markup( $dataset['earnings_gross']['relative_data'] ); ?> + + + +

    +
    +
    + + + + +
    +

    + # +

    +

    + +

    +

    + +

    +

    + build_relative_markup( $dataset['earnings_net']['relative_data'] ); ?> + + + +

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

    + # +

    +

    + +

    +

    + +

    +

    + build_relative_markup( $dataset['new_customers']['relative_data'] ); ?> + + + +

    +
    +
    + + + + +
    +

    + # +

    +

    + +

    +

    + +

    +

    + build_relative_markup( $dataset['average_order_value']['relative_data'] ); ?> + + + +

    +
    +
    diff --git a/includes/emails/email-summary/template-parts/email-styles.php b/includes/emails/email-summary/template-parts/email-styles.php new file mode 100644 index 00000000000..16139ef2ff7 --- /dev/null +++ b/includes/emails/email-summary/template-parts/email-styles.php @@ -0,0 +1,152 @@ + + diff --git a/includes/emails/email-summary/template-parts/fee-info.php b/includes/emails/email-summary/template-parts/fee-info.php new file mode 100644 index 00000000000..bb17cb5ea25 --- /dev/null +++ b/includes/emails/email-summary/template-parts/fee-info.php @@ -0,0 +1,77 @@ +application_fee->has_application_fee(); +$ignore_statuses = array( 'Not Connected', 'Not Supported' ); + +if ( false === $fees_status && in_array( edd_stripe()->application_fee->get_status(), $ignore_statuses, true ) ) { + return; +} + +if ( true === $fees_status && 'USD' !== edd_get_currency() ) { + return; +} + +$stats = new \EDD\Stats(); + +// Get the Stripe revenue so far this year, for the Stripe gateway. +$stripe_revenue = $stats->get_gateway_earnings( + array( + 'range' => 'this_year', + 'gateway' => 'stripe', + 'status' => edd_get_gross_order_statuses(), + 'type' => array( 'sale' ), + ) +); + +$savings = edd_stripe()->application_fee->get_application_fee_amount( $stripe_revenue ); + +if ( true === $fees_status && $savings < 100 ) { + return; +} + +if ( false === $fees_status && empty( $savings ) ) { + return; +} + +?> +
    +

    + +

    +

    + 'email-summaries', + 'utm_content' => 'stripe-fees', + ) + ); + + printf( + /* translators: 1: amount that could have been saved, 2: opening anchor tag, 3: closing anchor tag */ + esc_html__( 'You could have saved %1$s in transaction fees this year by %2$supgrading to an Extended Pass%3$s.', 'easy-digital-downloads' ), + edd_currency_filter( edd_format_amount( $savings ) ), + '', + '' + ); + } else { + printf( + /* translators: 1: opening span tag, 2. the formatted currency amount, 3. the closing span tag */ + esc_html__( 'You have %1$ssaved %2$s in transaction fees%3$s this year with your active license.', 'easy-digital-downloads' ), + '', + edd_currency_filter( edd_format_amount( $savings ) ), + '' + ); + } + ?> +

    +
    + +
    diff --git a/includes/emails/email-summary/template-parts/header.php b/includes/emails/email-summary/template-parts/header.php new file mode 100644 index 00000000000..5bae9d5d47c --- /dev/null +++ b/includes/emails/email-summary/template-parts/header.php @@ -0,0 +1,13 @@ + + diff --git a/includes/emails/email-summary/template-parts/preview-text.php b/includes/emails/email-summary/template-parts/preview-text.php new file mode 100644 index 00000000000..a7c8aa09572 --- /dev/null +++ b/includes/emails/email-summary/template-parts/preview-text.php @@ -0,0 +1,8 @@ + +
    + format( $wp_date_format ) ); ?> - format( $wp_date_format ) ); ?> +
    diff --git a/includes/emails/email-summary/template-parts/pro-tips.php b/includes/emails/email-summary/template-parts/pro-tips.php new file mode 100644 index 00000000000..a3ebd061a1a --- /dev/null +++ b/includes/emails/email-summary/template-parts/pro-tips.php @@ -0,0 +1,41 @@ + + diff --git a/includes/emails/email-summary/template-parts/site-info.php b/includes/emails/email-summary/template-parts/site-info.php new file mode 100644 index 00000000000..10706d2f5db --- /dev/null +++ b/includes/emails/email-summary/template-parts/site-info.php @@ -0,0 +1,42 @@ + +

    + +
    + format( $wp_date_format ) ); ?> - format( $wp_date_format ) ); ?> +
    + + + + + + +
    +

    +
    + + +

    + +

    diff --git a/includes/emails/email-summary/template-parts/top-products.php b/includes/emails/email-summary/template-parts/top-products.php new file mode 100644 index 00000000000..129d148d7b0 --- /dev/null +++ b/includes/emails/email-summary/template-parts/top-products.php @@ -0,0 +1,39 @@ + +
    +
    + # +
    + +
    + +
    + + + + + + + object instanceof \EDD_Download ) { + continue; + } + + $title = $product->object->post_title; + $revenue = edd_currency_filter( edd_format_amount( $product->total ) ); + ?> + + + + + +
    .
    +
    diff --git a/includes/emails/functions.php b/includes/emails/functions.php new file mode 100755 index 00000000000..085f7df7870 --- /dev/null +++ b/includes/emails/functions.php @@ -0,0 +1,235 @@ + 0 ? $emails : get_bloginfo( 'admin_email' ); + $emails = array_map( 'trim', explode( "\n", $emails ) ); + + /** + * Filters the emails for which admin notifications are sent. + * + * @param array $emails The emails to send admin notices to. + * @param null|EDD\Orders\Order $email Optional. The email object. Default: null. Added in 3.2.3. + */ + return apply_filters( 'edd_admin_notice_emails', $emails, $email ); +} + +/** + * Checks whether admin sale notices are disabled + * + * @since 1.5.2 + * + * @param int $payment_id The payment ID + * @return mixed + */ +function edd_admin_notices_disabled( $payment_id = 0 ) { + $email = edd_get_email( 'admin_order_notice' ); + $status = $email && $email->is_enabled(); + + return (bool) apply_filters( 'edd_admin_notices_disabled', empty( $status ), $payment_id ); +} + +/** + * Get an email. + * + * @since 3.3.0 + * + * @param int|string $email_id The email ID. This can also be the email_id because that's really how we identify emails. + * @return \EDD\Emails\Email|false + */ +function edd_get_email( $email_id = 0 ) { + $query = new Query(); + if ( ! is_numeric( $email_id ) ) { + return $query->get_item_by( 'email_id', $email_id ); + } + + return $query->get_item( $email_id ); +} + +/** + * Adds an email to the database. + * + * @since 3.3.0 + * @param array $args The query arguments. + * @return int|false + */ +function edd_add_email( $args = array() ) { + $query = new Query(); + + return $query->add_item( $args ); +} + +/** + * Updates an email in the database. + * + * @since 3.3.0 + * @param int $email_id The email ID. + * @param array $args The query arguments. + * @return bool + */ +function edd_update_email( $email_id = 0, $args = array() ) { + $query = new Query(); + + return $query->update_item( $email_id, $args ); +} + +/** + * Deletes an email from the database. + * + * @since 3.3.0 + * @param int $email_id The email ID. + * @return bool + */ +function edd_delete_email( $email_id = 0 ) { + $query = new Query(); + + return $query->delete_item( $email_id ); +} + +/** + * Gets an email by a field. + * + * @since 3.3.0 + * @param string $field The field to query by. + * @param string $value The value to query by. + * @return \EDD\Emails\Email + */ +function edd_get_email_by( $field = '', $value = '' ) { + $query = new Query(); + + return $query->get_item_by( $field, $value ); +} + +/** + * Gets emails. + * + * @since 3.3.0 + * @param array $args The query arguments. + * @return \EDD\Emails\Email[] + */ +function edd_get_emails( $args = array() ) { + + $r = wp_parse_args( + $args, + array( + 'number' => 300, + ) + ); + $query = new Query(); + + return $query->query( $r ); +} + +/** + * Add meta data field to an email. + * + * @since 3.3.0 + * + * @param int $email_id Order 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_email_meta( $email_id, $meta_key, $meta_value, $unique = false ) { + return add_metadata( 'edd_email', $email_id, $meta_key, $meta_value, $unique ); +} + +/** + * Remove meta data matching criteria from an email. + * + * 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.3.0 + * + * @param int $email_id Order 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_email_meta( $email_id, $meta_key, $meta_value = '' ) { + return delete_metadata( 'edd_email', $email_id, $meta_key, $meta_value ); +} + +/** + * Retrieve email meta field for an email. + * + * @since 3.3.0 + * + * @param int $email_id Order 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_email_meta( $email_id, $key = '', $single = false ) { + return get_metadata( 'edd_email', $email_id, $key, $single ); +} + +/** + * Update email meta field based on email ID. + * + * Use the $prev_value parameter to differentiate between meta fields with the + * same key and email ID. + * + * If the meta field for the email does not exist, it will be added. + * + * @since 3.3.0 + * + * @param int $email_id Email 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_email_meta( $email_id, $meta_key, $meta_value, $prev_value = '' ) { + return update_metadata( 'edd_email', $email_id, $meta_key, $meta_value, $prev_value ); +} + +/** + * Delete everything from email meta matching meta key. + * + * @since 3.3.0 + * @param string $meta_key Key to search for when deleting. + * @return bool Whether the email meta key was deleted from the database. + */ +function edd_delete_email_meta_by_key( $meta_key ) { + return delete_metadata( 'edd_email', null, $meta_key, '', true ); +} diff --git a/includes/emails/recapture.php b/includes/emails/recapture.php new file mode 100644 index 00000000000..c3881af47bc --- /dev/null +++ b/includes/emails/recapture.php @@ -0,0 +1,150 @@ + __( 'You do not have permission to do this.', 'easy-digital-downloads' ), + ) + ); + } + $nonce = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( ! $nonce || ! wp_verify_nonce( $nonce, 'edd-recapture-connect' ) ) { + wp_send_json_error( + array( + 'error' => __( 'Nonce verification failed.', '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. tag, 3. tag, 4. 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">', + '', + ' + ' + ) + ); + ?> +
    + + + + + + + + context; + $recipient = $email->recipient; + if ( ! $email->get_template()->supports_html() ) { + $content = __( 'This email will be sent as a plain text email and does not support images or HTML markup.', 'easy-digital-downloads' ); + if ( 'text/html' === EDD()->emails->get_content_type() ) { + $content .= ' ' . __( 'This is specific to this email and does not affect other emails.', 'easy-digital-downloads' ); + } + $tooltip = new \EDD\HTML\Tooltip( + array( + 'content' => $content, + 'dashicon' => 'dashicons-warning', + ) + ); + $tooltip->output(); + } + } + } + if ( wp_script_is( 'edd-admin-email-tags' ) ) { + return; + } + // Output Thickbox content. + edd_email_tags_inserter_thickbox_content( $context, $recipient ); + // Enqueue scripts. + edd_email_tags_inserter_enqueue_scripts( $context, $recipient ); +} + +/** + * Enqueue scripts for clicking a tag inside of Thickbox. + * + * @since 3.0 + * @param string $context The context to get tags for. + * @param string $recipient The recipient to get tags for. + */ +function edd_email_tags_inserter_enqueue_scripts( $context = '', $recipient = '' ) { + + wp_enqueue_style( 'edd-admin-email-tags' ); + wp_enqueue_script( 'edd-admin-email-tags' ); + + // Send information about tags to script. + $items = array(); + $tags = edd_get_email_tags( $context, $recipient ); + + foreach ( $tags as $tag ) { + $items[] = array( + 'title' => $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 + * @param string $context The context to get tags for. + * @param string $recipient The recipient to get tags for. + */ +function edd_email_tags_inserter_thickbox_content( $context = '', $recipient = '' ) { + $tags = edd_get_email_tags( $context, $recipient ); + ?> + + email_tags->add( $tag, $description, $func, $label, $contexts, $recipients ); +} + +/** + * 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. + * @param array $context Context in which the email is being sent. + * @param string $recipient The recipient of the email. + * @return bool + */ +function edd_email_tag_exists( $tag, $context = '', $recipient = '' ) { + return EDD()->email_tags->email_tag_exists( $tag, $context, $recipient ); +} + +/** + * Get all email tags + * + * @since 1.9 + * @param string $context Context in which the email is being sent. + * @param string $recipient The recipient of the email. + * @return array + */ +function edd_get_email_tags( $context = '', $recipient = '' ) { + return EDD()->email_tags->get( $context, $recipient ); +} + +/** + * 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 Object ID. + * @param object $email_object This could be an order, a user, license, subscription, etc. + * @param string|\EDD\Emails\Types\Email $context Context in which the email is being sent. This should match the object if provided. + * + * @return string Content with email tags filtered out. + */ +function edd_do_email_tags( $content = '', $order_id = 0, $email_object = null, $context = 'order' ) { + + // Load email tags. + edd_load_email_tags(); + + // Replace all tags. + $content = EDD()->email_tags->do_tags( $content, $order_id, $email_object, $context ); + + if ( 'order' === $context && false !== has_filter( 'edd_email_template_tags' ) ) { + // Maintaining backwards compatibility. + $content = apply_filters( 'edd_email_template_tags', $content, edd_get_payment_meta( $order_id ), $order_id ); + } + + return $content; +} + +/** + * Load email tags. + * + * @since 1.9 + * @since 3.3.4 No longer loads on `init`; called as needed. + */ +function edd_load_email_tags() { + if ( ! did_action( 'edd_add_email_tags' ) ) { + do_action( 'edd_add_email_tags' ); + } +} + +/** + * Add default EDD email template tags. + * + * @since 1.9 + */ +function edd_setup_email_tags() { + $tags = new EDD\Emails\Tags\Registry(); + $tags->register(); +} +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, $order = null ) { + if ( ! $order ) { + $order = edd_get_order( $payment_id ); + } + if ( ! $order ) { + return ''; + } + + $download_list = '
      '; + $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; + } + + if ( has_filter( 'edd_email_receipt_download_title' ) ) { + $payment = edd_get_payment( $payment_id ); + $cart_items = $payment->cart_details; + $title = apply_filters( + 'edd_email_receipt_download_title', + $title, + $cart_items[ $item->cart_index ], + $item->price_id, + $payment_id + ); + } + $download_list .= '
    • ' . $title . '
      '; + } + + $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; + $show_links = apply_filters( 'edd_email_show_links', true ); + + 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 = $show_links ? 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 = $show_links ? 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, $email_object = null, $context = 'order' ) { + $context = EDD()->email_tags->get_context( $context ); + if ( 'user' === $context ) { + if ( ! $email_object instanceof WP_User ) { + $email_object = new WP_User( $payment_id ); + } + + return ! empty( $email_object->first_name ) ? $email_object->first_name : ''; + } + $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 The ID of the object of the email. + * @param null|mixed $email_object The object of the email. + * @param string $context The context of the email. + * + * @return string fullname + */ +function edd_email_tag_fullname( $payment_id, $email_object = null, $context = 'order' ) { + $context = EDD()->email_tags->get_context( $context ); + if ( 'user' === $context ) { + if ( ! $email_object instanceof WP_User ) { + $email_object = new WP_User( $payment_id ); + } + + return ! empty( $email_object->display_name ) ? $email_object->display_name : ''; + } + + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + + $customer = edd_get_customer( $email_object->customer_id ); + + return ! empty( $customer->name ) ? $customer->name : ''; +} + +/** + * Email template tag: username + * The buyer's user name on the site, if they registered an account + * + * @param int $object_id The object ID. This can be an order ID or user ID. + * @param null|mixed $email_object The object of the email. + * @param string $context The context of the email. + * @return string username + */ +function edd_email_tag_username( $object_id, $email_object = null, $context = 'order' ) { + $context = EDD()->email_tags->get_context( $context ); + if ( 'user' === $context ) { + if ( ! $email_object instanceof WP_User ) { + $email_object = new WP_User( $object_id ); + } + + return ! empty( $email_object->user_login ) ? $email_object->user_login : ''; + } + + $payment = new EDD_Payment( $object_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 $object_id The object ID. This can be an order ID or user ID. + * @param null|mixed $email_object The object of the email. + * @param string $context The context of the email. + * @return string user_email + */ +function edd_email_tag_user_email( $object_id, $email_object = null, $context = 'order' ) { + $context = EDD()->email_tags->get_context( $context ); + if ( ! in_array( $context, array( 'order', 'user' ), true ) ) { + return ''; + } + if ( 'user' === $context ) { + if ( ! $email_object instanceof WP_User ) { + $email_object = new WP_User( $object_id ); + } + + return ! empty( $email_object->user_email ) ? $email_object->user_email : ''; + } + + $order = edd_get_order( $object_id ); + + return $order->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 The ID of the object of the email. + * @param null|mixed $email_object The object of the email. + * + * @return string date + */ +function edd_email_tag_date( $payment_id, $email_object = null ) { + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + + return date_i18n( get_option( 'date_format' ), strtotime( $email_object->date_created ) ); +} + +/** + * Email template tag: subtotal + * Price of purchase before taxes + * + * @param int $payment_id The ID of the object of the email. + * @param null|mixed $email_object The object of the email. + * + * @return string subtotal + */ +function edd_email_tag_subtotal( $payment_id, $email_object = null ) { + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + $subtotal = edd_currency_filter( edd_format_amount( $email_object->subtotal ), $email_object->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, $email_object = null ) { + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + $tax = edd_currency_filter( edd_format_amount( $email_object->tax ), $email_object->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, $email_object = null ) { + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + $price = edd_currency_filter( edd_format_amount( $email_object->total ), $email_object->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, $email_object = null ) { + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + + return $email_object->get_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, $email_object = null ) { + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + + return $email_object->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, $email_object = null ) { + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + + return edd_get_gateway_checkout_label( $email_object->gateway, $email_object ); +} + +/** + * 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 int $order_id + * @return string + */ +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() + ) + ); + if ( 'none' === edd_get_option( 'email_template' ) ) { + return $receipt_url; + } + + /* translators: 1: opening anchor tag, 2: closing anchor tag */ + return sprintf( __( '%1$sView it in your browser %2$s', 'easy-digital-downloads' ), '', '»' ); +} + +/** + * 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 $email_object_id + * @return string IP address + */ +function edd_email_tag_ip_address( $email_object_id, $email_object = null, $context = 'order' ) { + $context = EDD()->email_tags->get_context( $context ); + if ( 'user' === $context && ! is_null( $email_object ) ) { + return $_SERVER['REMOTE_ADDR']; + } + + if ( ! $email_object instanceof EDD\Orders\Order ) { + $email_object = edd_get_order( $payment_id ); + } + + return $email_object->ip; +} + +/** + * Get various correctly formatted names used in emails + * + * @since 1.9 + * @since 3.2.0 - Moved to the tags.php file, as it is exclusively is used for email tags, even in extensions. + * + * @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; +} diff --git a/includes/emails/template.php b/includes/emails/template.php new file mode 100755 index 00000000000..7cc404f1ac3 --- /dev/null +++ b/includes/emails/template.php @@ -0,0 +1,174 @@ +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 + * + * @todo Currently this has a hardcoded set of tags for replacement, and doesn't include + * all tags registered, when we udpate the tag registration we should update to allow adding a 'sample' data + * for each tag. + * + * @since 1.0 + * @since 3.2.0 - Added $wpautop parameter. + * @param string $message Email message with template tags + * @param bool $disable_wpautop If we should fully disable wpautop for this content. + * + * @return string $message Fully formatted message + */ +function edd_email_preview_template_tags( $message, $disable_wpautop = false, $order_id = 0 ) { + + $notes = __( 'These are some sample notes added to a product.', 'easy-digital-downloads' ); + $message = str_replace( '{product_notes}', $notes, $message ); + + if ( empty( $order_id ) ) { + $order_numbers = new EDD\Orders\Number(); + $order_number = $order_numbers->format( wp_rand( 100, 987 ) ); + $price = edd_currency_filter( edd_format_amount( 10.50 ) ); + $gateway = edd_get_gateway_admin_label( edd_get_default_gateway() ); + $receipt_id = strtolower( md5( uniqid() ) ); + $tax = edd_currency_filter( edd_format_amount( 1.00 ) ); + $sub_total = edd_currency_filter( edd_format_amount( 9.50 ) ); + $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( '{payment_id}', $order_number, $message ); + $message = str_replace( '{receipt_link}', edd_email_tag_receipt_link( 0 ), $message ); + $message = str_replace( '{receipt}', edd_email_tag_receipt( 0 ), $message ); + } + + $user = wp_get_current_user(); + $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 = apply_filters( 'edd_email_preview_template_tags', $message ); + + $wpautop = $disable_wpautop ? false : apply_filters( 'edd_email_preview_template_wpautop', true ); + + return $wpautop ? wpautop( $message ) : $message; +} + +/** + * 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\Sessions\Handler::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\Sessions\Handler::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\Sessions\Handler::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\Sessions\Handler::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\Sessions\Handler::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..e850649fda2 --- a/includes/formatting.php +++ b/includes/formatting.php @@ -2,118 +2,359 @@ /** * 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 'data': + $return_value = $formatter->format_for_data( $decimals )->data_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( esc_html( $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 * - * Returns a nicely formatted amount. + * @since 1.4.2 + * @since 3.0 Updated to allow currency to be passed in. * - * @access public - * @since 1.0 - * @param $amount string the price amount to format - * @return string - the newly formatted amount + * @param int $decimals Number of decimal places. + * @param string $currency Currency. + * + * @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(), + ), + 'ol' => array( + 'class' => array(), + 'id' => array(), + ), + 'li' => array( + 'class' => array(), + 'id' => array(), + ), + 'blockquote' => 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/actions.php b/includes/gateways/actions.php new file mode 100755 index 00000000000..59d1c6a209a --- /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( 'edd_before_checkout_cart', 'edd_no_gateway_error', 5 ); diff --git a/includes/gateways/amazon-payments.php b/includes/gateways/amazon-payments.php new file mode 100644 index 00000000000..12910bb648c --- /dev/null +++ b/includes/gateways/amazon-payments.php @@ -0,0 +1,1215 @@ +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 ); + 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_action( 'admin_notices', array( $this, 'amazon_payments_notice' ) ); + add_filter( 'edd_payment_details_transaction_id-' . $this->gateway_id, array( $this, 'link_transaction_id' ), 10, 2 ); + } + } + + /** + * Adds an admin notice to switch to Stripe. + * This notice is only shown if the user has Amazon Payments enabled. + * + * @todo Update words, especially if Stripe is already active. + * @since 3.2.0 + * @return void + */ + public function amazon_payments_notice() { + if ( ! edd_is_gateway_active( $this->gateway_id ) ) { + return; + } + $stripe_url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + 'section' => 'edd-stripe', + ) + ); + $benefits_url = edd_link_helper( + 'https://easydigitaldownloads.com/edd-stripe-integration', + array( + 'utm_medium' => 'admin-notice', + 'utm_content' => 'amazon-deprecated-notice', + ), + false + ); + EDD()->notices->add_notice( + array( + 'id' => 'amazon-gateway-notice', + 'class' => 'error', + 'message' => sprintf( + /* translators: %1$s Opening anchor tag, %2$s Closing anchor tag, %3$s Opening anchor tag */ + __( 'Amazon Payments for Easy Digital Downloads has been deprecated and will be removed in a future version. To continue to accept credit card payments in the future, please %1$senable Stripe now%2$s or %3$slearn more about the benefits of using Stripe%2$s.', 'easy-digital-downloads' ), + '', + '', + '', + ), + 'is_dismissible' => false, + ) + ); + } + + /** + * 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 ( ! is_null( $this->is_setup ) ) { + return $this->is_setup; + } + + $this->is_setup = edd_is_gateway_setup( 'amazon' ); + + 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' ); + } + + /** + * 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' ), + /* translators: %s: Integration Settings URL */ + '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']; + /* translators: %s: Payment Failure Reason (dynamic, provided by the gateway) */ + 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 { + /* translators: %s: Amazon Error (dynamic, provided by the gateway) */ + 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' ); + + /* translators: %s: Amazon Refund ID */ + 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' : + /* translators: %s: Amazon Refund ID */ + $note = __( 'Refund declined in Amazon. Refund ID: %s', 'easy-digital-downloads' ); + break; + + case 'Completed' : + $refund_id = $refund['RefundResult']['RefundDetails']['AmazonRefundId']; + /* translators: %s: Amazon Refund ID */ + $note = sprintf( __( 'Refund completed in Amazon. Refund ID: %s', 'easy-digital-downloads' ), $refund_id ); + break; + + case 'Pending' : + /* translators: %s: Amazon Refund ID */ + $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..0c239e4a2b2 --- /dev/null +++ b/includes/gateways/functions.php @@ -0,0 +1,679 @@ + 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 + * @since 3.3.5 Added optional $order parameter. + * + * @param string $gateway Name of the gateway to retrieve a label for. + * @param EDD\Orders\Order $order The order object (optional). + * @return string Gateway admin label + */ +function edd_get_gateway_admin_label( $gateway, $order = null ) { + $gateways = edd_get_payment_gateways(); + + $label = isset( $gateways[ $gateway ] ) + ? $gateways[ $gateway ]['admin_label'] + : ucwords( $gateway ); + + /** + * Filter the admin label for the specified gateway. + * + * @since 3.3.5 Added optional $order parameter. + * @param string $label Checkout label for the gateway. + * @param string $gateway Name of the gateway to retrieve a label for. + * @param EDD\Orders\Order $order The order object. + */ + return apply_filters( 'edd_gateway_admin_label', $label, $gateway, $order ); +} + +/** + * Returns the checkout label for the specified gateway. + * + * @since 3.3.5 Added optional $order parameter. + * @param string $gateway Name of the gateway to retrieve a label for. + * @param EDD\Orders\Order $order The order object (optional). + * @return string Checkout label for the gateway. + */ +function edd_get_gateway_checkout_label( $gateway, $order = null ) { + $gateways = edd_get_payment_gateways(); + $label = isset( $gateways[ $gateway ] ) ? $gateways[ $gateway ]['checkout_label'] : $gateway; + + /** + * Filter the checkout label for the specified gateway. + * + * @param string $label Checkout label for the gateway. + * @param string $gateway Name of the gateway to retrieve a label for. + * @param EDD\Orders\Order $order The order object. + */ + return apply_filters( 'edd_gateway_checkout_label', $label, $gateway, $order ); +} + +/** + * 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 The ID of the download being purchased. + * @param array $options The options for the download. + * @param int $quantity The quantity of the download being purchased. + * + * @return array $purchase_data The purchase data. + */ +function edd_build_straight_to_gateway_data( $download_id = 0, $options = array(), $quantity = 1 ) { + $price_options = array( + 'price_id' => null, + ); + + 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, + ); + $price = $prices[ $price_id ]['amount']; + } + + // Set up Downloads array. + $downloads = array( + array( + 'id' => $download_id, + 'options' => $price_options, + ), + ); + + foreach ( $downloads as $download ) { + edd_add_to_cart( $download['id'], $download['options'] ); + } + + // Setup user information. + $user_info = array( + 'id' => 0, + 'email' => '', + 'first_name' => '', + 'last_name' => '', + 'discount' => 'none', + 'address' => array(), + ); + if ( is_user_logged_in() ) { + $current_user = wp_get_current_user(); + $user_info['id'] = $current_user->ID; + $user_info['email'] = $current_user->user_email; + $user_info['first_name'] = $current_user->user_firstname; + $user_info['last_name'] = $current_user->user_lastname; + } + + // Setup purchase information. + $purchase_data = array( + 'downloads' => edd_get_cart_contents(), + 'fees' => edd_get_cart_fees(), + 'subtotal' => $price * $quantity, + 'discount' => 0, + 'tax' => 0, + 'price' => $price * $quantity, + 'purchase_key' => edd_generate_order_payment_key( $user_info['email'] ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'post_data' => array(), + 'cart_details' => edd_get_cart_content_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. + * @param bool $ignore_registration Whether or not to ignore if the gateway is registered. + * @return bool True if the gateway is setup, false otherwise. + */ +function edd_is_gateway_setup( $gateway = '', $ignore_registration = false ) { + // 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 ) && ! $ignore_registration ) { + 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; + + case 'amazon': + $amazon_settings = array( + 'amazon_seller_id', + 'amazon_client_id', + 'amazon_mws_access_key', + 'amazon_mws_secret_key', + ); + + $is_setup = true; + foreach ( $amazon_settings as $key ) { + if ( empty( edd_get_option( $key, '' ) ) ) { + $is_setup = false; + break; + } + } + 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; + + case 'amazon': + $gateway_settings_url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + 'section' => 'amazon', + ) + ); + 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; +} + +/** + * Checks whether the current cart setup is supported. This is intended for subscription orders. + * If the cart contains multiple products and one of them is a subscription, we need to check + * if the gateway supports a mixed cart. + * + * @since 3.2.7 + * @return bool + */ +function edd_gateway_supports_cart_contents( string $gateway ) { + + // If the cart only contains a single item, the cart is supported. + if ( count( edd_get_cart_contents() ) === 1 ) { + return true; + } + + // If Recurring isn't active, or if the cart doesn't contain a subscription, the cart is supported. + if ( ! function_exists( 'edd_recurring' ) || ! edd_recurring()->cart_contains_recurring() ) { + return true; + } + + // If the cart is mixed and the gateway supports it, the cart is supported. + // Historically, mixed carts also support multiple subscriptions. + return in_array( 'mixed_cart', edd_get_gateway_supports( $gateway ), true ); +} 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..66833d9d0b1 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 Gateway does not need a CC form, so remove it. * - * Manual 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 -*/ - -function edd_manual_payment($purchase_data) { - global $edd_options; + * @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 ) ); + } - /* - * 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,30 @@ 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'], + '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'], + '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']); + /* translators: %s: payment data */ + 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..61fe7a327a5 --- a/includes/gateways/paypal-standard.php +++ b/includes/gateways/paypal-standard.php @@ -2,333 +2,1410 @@ /** * PayPal Standard Gateway * - * @package Easy Digital Downloads - * @subpackage PayPal Standard Gateway - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD\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' ); + +/** + * 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' ); + } -function edd_paypal_remove_cc_form() { - // we only register the action so that the default CC form is not shown + 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( + /* translators: %s: Documentation URL */ + __( '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( + /* translators: %s: FAQ URL */ + __( '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( + /* translators: %s: PayPal API Credentials URL */ + __( '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'], + 'user_email' => $purchase_data['user_email'], + 'purchase_key' => $purchase_data['purchase_key'], + 'downloads' => $purchase_data['downloads'], + 'user_info' => $purchase_data['user_info'], + 'cart_details' => $purchase_data['cart_details'], + 'gateway' => 'paypal', + 'status' => 'pending', + ); + if ( ! empty( $purchase_data['date'] ) ) { + $payment_data['date'] = $purchase_data['date']; + } + + // Record the pending order. + $order_id = edd_build_order( $payment_data ); + + // Check payment. + if ( ! $order_id ) { + // Record the error. + /* translators: %s: payment data */ + 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 ) ), $order_id ); + // 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', $order_id ); + + // Get the success url. + $return_url = add_query_arg( + array( + 'payment-confirmation' => 'paypal', + 'payment-id' => urlencode( $order_id ), + ), + edd_get_confirmation_page_uri() + ); + + // 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' => $order_id, + 'rm' => '2', + 'return' => esc_url_raw( $return_url ), + 'cancel_return' => esc_url_raw( edd_get_failed_transaction_uri( '?payment-id=' . sanitize_key( $order_id ) ) ), + '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; + } elseif ( 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 - * - * Listens for a PayPal IPN requests and then sends to the processing function. + * Add paypal.com to the list of allowed hosts that wp_safe_redirect can redirect to. * - * @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'] ) && 'POST' !== $_SERVER['REQUEST_METHOD'] ) { + 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 { - // 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' ); - } - - } + // 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' ), + /* translators: %s: IPN Verification response */ + 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' ), + /* translators: %s: IPN Verification response */ + 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 { + // 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' ), + /* translators: %s: IPN Verification response */ + 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' ), + /* translators: %s: IPN Verification response */ + 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' ), + /* translators: %s: IPN Verification response */ + 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: ' . var_export( $data ) ); + 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: ' . var_export( $data ) ); + edd_record_gateway_error( + __( 'IPN Error', 'easy-digital-downloads' ), + /* translators: %s: IPN Verification response */ + 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() ) { + /* translators: %s: PayPal Transaction ID */ + 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' ); + + } elseif ( '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 ) ) { + /* translators: %s: PayPal transaction ID */ + 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. + + } + + /* translators: 1: PayPal transaction ID, 2: Reason for refund */ + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Payment #%1$s Refunded for reason: %2$s', 'easy-digital-downloads' ), $data['parent_txn_id'], $data['reason_code'] ) ); + /* translators: %s: PayPal transaction ID */ + 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 + * Get PayPal Redirect * - * @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 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 { - $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 + + // 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 ) { + + $order_id = filter_input( INPUT_GET, 'payment-id', FILTER_SANITIZE_NUMBER_INT ); + $session = edd_get_purchase_session(); + if ( ! $order_id && ! empty( $session['purchase_key'] ) ) { + $order_id = edd_get_purchase_id_by_key( $session['purchase_key'] ); + } + if ( ! $order_id && ! $session ) { + return $content; + } + + edd_empty_cart(); + + $payment_confirmation = filter_input( INPUT_GET, 'payment-confirmation', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( 'paypal' !== $payment_confirmation ) { + return $content; + } + + $order = edd_get_order( $order_id ); + if ( $order && 'pending' === $order->status ) { + + // Payment is still pending so show processing indicator to fix the Race Condition, issue #. + ob_start(); + + edd_get_template_part( 'payment', 'processing' ); + + return 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(); + // If there is no purchase session, don't try and fire PDT. + if ( empty( $purchase_session ) ) { + return; + } + + $payment = edd_get_payment( $payment_id ); + + // 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']; + + } elseif ( isset( $data['payment_gross'] ) ) { + + $total = $data['payment_gross']; + + } elseif ( 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' ); + /* translators: 1: Expected payment amount, 2: Received payment amount */ + $payment->add_note( sprintf( __( 'Payment failed while validating PayPal PDT. Amount expected: %1$f. Amount Received: %2$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(); + } + + if ( 'pending' === $payment->status ) { + edd_set_purchase_session( + array( + 'purchase_key' => $payment->key, + ) + ); + } + } 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' ); + +/** + * Given a Payment ID, extract the transaction ID + * + * @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 ) { + + $order = edd_get_order( $payment_id ); + $sandbox = 'test' === $order->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 The order object. + * + * @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 { + /* 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: 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: PayPal 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, + /* translators: %s: error message. */ + '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..f8416fc9b2c --- /dev/null +++ b/includes/gateways/paypal/admin/connect.php @@ -0,0 +1,922 @@ +signupLink ) ) { + ?> +
    +

    + tag, 2. 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, + ); + } + } + + $request = new \EDD\Utils\RemoteRequest( + 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(), + ) + ), + 'method' => 'POST', + ) + ); + + if ( is_wp_error( $request->response ) ) { + + return array( + 'code' => $request->code, + 'body' => $request->response->get_error_message(), + ); + } + + $body = json_decode( $request->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' => $request->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: 1: HTTP response code, 2: error message */ + __( 'Unexpected response code: %1$d. Error: %2$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; + + // Store a transient to indicate that we've started the connect process. + set_transient( + 'edd_paypal_commerce_connect_started_' . $mode, + wp_hash( get_current_user_id() . '_' . $mode . '_started', 'nonce' ), + 15 * MINUTE_IN_SECONDS + ); + + $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' ), + 'method' => 'POST', + ); + + /* + * First get a temporary access token from PayPal. + */ + $request = new \EDD\Utils\RemoteRequest( + $api_url . 'v1/oauth2/token', + $api_args + ); + + if ( is_wp_error( $request->response ) ) { + wp_send_json_error( $request->response->get_error_message() ); + } + + $body = json_decode( $request->body ); + + 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' ), + $request->code + ) + ); + } + + /* + * Now we can use this access token to fetch the seller's credentials for all future + * API requests. + */ + $request = new \EDD\Utils\RemoteRequest( + $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( $request->response ) ) { + wp_send_json_error( $request->response->get_error_message() ); + } + + $code = $request->code; + $body = json_decode( $request->body ); + + 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(), true ) ) { + 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(), true ) ) { + 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 ) { + // We don't want to stop the process if we can't delete the webhooks. + } + + // 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 ) { + // We don't want to stop the process if we can't delete the webhook. + } + + 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 ) { + // We don't want to stop the process if we can't delete the webhooks. + } + + // 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 ) { + // We don't want to stop the process if we can't delete the webhooks. + } + + // 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 + * @throws \Exception If the merchant ID is not found. + */ +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( + 'load-download_page_edd-settings', + function () { + if ( ! isset( $_GET['merchantIdInPayPal'] ) || ! edd_is_admin_page( 'settings' ) ) { + return; + } + + $mode = edd_is_test_mode() ? 'sandbox' : 'live'; + $connect_process = get_transient( 'edd_paypal_commerce_connect_started_' . $mode ); + if ( empty( $connect_process ) ) { + return; + } + $check = wp_hash( get_current_user_id() . '_' . $mode . '_started', 'nonce' ); + + if ( ! hash_equals( $connect_process, $check ) ) { + wp_die( + __( 'There was an error processing the connection to PayPal. Please attempt to connect again.', 'easy-digital-downloads' ), + __( 'Error', 'easy-digital-downloads' ), + array( + 'response' => 403, + 'link_text' => __( 'Return to settings', 'easy-digital-downloads' ), + 'link_url' => get_settings_url(), + ) + ); + } + + 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(); + + // Remove our transient, instead of waiting for it to be removed automatically. + delete_transient( 'edd_paypal_commerce_connect_started_' . $mode ); + + edd_redirect( esc_url_raw( get_settings_url() ) ); + } +); + +/** + * Retrieves the merchant's status in PayPal. + * + * @param string $merchant_id The merchant ID to check. + * @param string $nonce The nonce to use for the request. + * + * @return array + * @throws PayPal\Exceptions\API_Exception If the request fails. + */ +function get_merchant_status( $merchant_id, $nonce = '' ) { + $api = new API(); + + $response = $api->make_request( + sprintf( + 'v1/customer/partners/%s/merchant-integrations/%s', + \EDD\Gateways\PayPal\get_partner_merchant_id(), + $merchant_id + ), + array(), + array(), + 'GET' + ); + + if ( 200 === (int) $api->last_response_code ) { + return $response; + } + + $response = (array) $response; + + if ( ! empty( $response['error'] ) ) { + $error_message = $response['error']; + } elseif ( ! empty( $response['message'] ) ) { + $error_message = $response['message']; + } else { + $error_message = sprintf( + 'Invalid HTTP response code: %d. Response: %s', + $api->last_response_code, + $response + ); + } + + // If the response code is a string, we'll default to a 403 because the API Exception requires an integer. + if ( ! is_int( $api->last_response_code ) ) { + throw new PayPal\Exceptions\API_Exception( $error_message, 403 ); + } + + throw new PayPal\Exceptions\API_Exception( $error_message, $api->last_response_code ); +} diff --git a/includes/gateways/paypal/admin/notices.php b/includes/gateways/paypal/admin/notices.php new file mode 100644 index 00000000000..04d4432e1aa --- /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..bdc6e2eb216 --- /dev/null +++ b/includes/gateways/paypal/admin/settings.php @@ -0,0 +1,181 @@ + 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'] = _x( + '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.', + "It is important to escape any quotations within this string, specifically \'Sandhills Development, LLC\'", + '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 + */ +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, + ), + ) + ); + ?> +

    +
    + supports_buy_now( $price_id ); + + if ( false === $supports_buy_now ) { + 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() ) { + return; + } + + $download = new \EDD_Download( $download_id ); + if ( empty( $download ) ) { + return; + } + + // Use the Download Class to determine if it supports Buy Now. + $price_id = is_numeric( $args['price_id'] ) ? absint( $args['price_id'] ) : null; + $supports_buy_now = $download->supports_buy_now( $price_id ); + + if ( false === $supports_buy_now ) { + return; + } + + 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 = wp_parse_args( + $purchase_data, + array( + 'currency' => edd_get_currency(), + 'status' => 'pending', + 'gateway' => 'paypal_commerce', + ) + ); + + $payment_id = edd_build_order( $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 + * @throws Gateway_Exception If an error occurs. + */ +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 = false; + $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: PayPal 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..b48cde5904d --- /dev/null +++ b/includes/gateways/paypal/class-merchant-account.php @@ -0,0 +1,207 @@ + $value ) { + if ( ! property_exists( $this, $key ) ) { + continue; + } + + $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..6e832e9dd64 --- /dev/null +++ b/includes/gateways/paypal/class-paypal-api.php @@ -0,0 +1,279 @@ +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..d0e18e0bece --- /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: 1: Response code, 2: Response message */ + __( 'Response Code: %1$d; Message: %2$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..87cd65a9389 --- /dev/null +++ b/includes/gateways/paypal/gateway-filters.php @@ -0,0 +1,111 @@ +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' ); + +/** + * Adds a link to the dispute within PayPal. + * + * @since 3.2.0 + * @param string $dispute_id + * @param EDD\Orders\Order $order + * @return string + */ +function link_dispute_id( $dispute_id, $order ) { + $subdomain = 'test' === $order->mode ? 'sandbox.' : ''; + $url = 'https://www.' . urlencode( $subdomain ) . 'paypal.com/resolutioncenter/view/' . urlencode( $dispute_id ) . '/inquiry'; + + return '' . esc_html( $dispute_id ) . ''; +} +add_filter( 'edd_payment_details_dispute_id_paypal_commerce', __NAMESPACE__ . '\link_dispute_id', 10, 2 ); + +/** + * Adds PayPal-specific hold reasons. + * + * @since 3.2.0 + * @param array $reasons + * @return array + */ +function add_hold_reasons( $reasons ) { + $paypal_reasons = array( + 'MERCHANDISE_OR_SERVICE_NOT_RECEIVED' => __( 'Merchandise or service not received', 'easy-digital-downloads' ), + 'MERCHANDISE_OR_SERVICE_NOT_AS_DESCRIBED' => __( 'Merchandise or service not as described', 'easy-digital-downloads' ), + 'UNAUTHORISED' => __( 'Unauthorized', 'easy-digital-downloads' ), + 'ITEM_NOT_RECEIVED' => __( 'Item not received', 'easy-digital-downloads' ), + 'UNAUTHORIZED_TRANSACTION' => __( 'Unauthorized transaction', 'easy-digital-downloads' ), + 'BUYER_COMPLAINT' => __( 'Buyer complaint', 'easy-digital-downloads' ), + ); + + return array_merge( $reasons, $paypal_reasons ); +} +add_filter( 'edd_order_hold_reasons', __NAMESPACE__ . '\add_hold_reasons' ); 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 @@ +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; + } + ?> +
    +
    + status ? 'disabled' : '' ); ?> + > + +
    + status ) : ?> +

    + +

    + +
    + 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: 1: Refund ID, 2: Error message */ + __( 'Failure when processing PayPal refund #%1$d: %2$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: 1: Response code, 2: Response message */ + __( 'Unexpected response code: %1$d. Response: %2$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: amount refunded; 2: 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..c74c8cb2556 --- /dev/null +++ b/includes/gateways/paypal/scripts.php @@ -0,0 +1,199 @@ + 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 The URL for the script source. + * + * @since 2.11 + * @return string + */ +function remove_ver_query_arg( $url ) { + // Account for a possibly empty URL here. + if ( empty( $url ) ) { + return $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..91433c32279 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,276 @@ * * @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() ) { + edd_set_error( 'empty_cart', __( 'Your cart is empty.', 'easy-digital-downloads' ) ); + } - // validate the form $_POST data - $valid_data = edd_purchase_form_validate_fields(); + 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 ); + } - // allow themes and plugins to hoook to errors - do_action('edd_checkout_error_checks', $_POST); + $is_ajax = ! empty( $_POST['edd_ajax'] ); + if ( $is_ajax && ! edds_verify() ) { + edd_set_error( 'checkout-nonce-error', __( 'Error processing purchase. Please reload the page and try again.', 'easy-digital-downloads' ) ); + } - // 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; + // Process the login form. + if ( isset( $_POST['edd_login_submit'] ) ) { + edd_process_purchase_login(); } - // 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; + $purchase_data = EDD\Sessions\PurchaseData::start( $is_ajax ); + if ( empty( $purchase_data ) || edd_get_errors() ) { + 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'], - 'first_name' => $user['user_first'], - 'last_name' => $user['user_last'], - 'discount' => $valid_data['discount'] - ); + // 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 ) { + + if ( ! is_user_logged_in() ) { + return; + } - // 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, - 'cart_details' => edd_get_cart_content_details(), - 'gateway' => $valid_data['gateway'], - 'card_info' => $valid_data['cc_info'] + // If the email has already been validated, skip this check. + if ( EDD()->session->get( 'email_validated' ) ) { + return; + } + + $user = wp_get_current_user(); + $email = $user->user_email; + $emails_to_check = array( + $email, + strtolower( $email ), ); - - // add the user data for hooks - $valid_data['user'] = $user; - - // 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 logged in user was validated. + if ( isset( $valid_data['logged_in_user']['user_email'] ) ) { + $email = $valid_data['logged_in_user']['user_email']; + $emails_to_check[] = $email; + $emails_to_check[] = strtolower( $email ); + } elseif ( isset( $valid_data['login_user_data']['user_email'] ) ) { + // If the user is logging in. + $email = $valid_data['login_user_data']['user_email']; + $emails_to_check[] = $email; + $emails_to_check[] = strtolower( $email ); + } + $emails_to_check = array_unique( $emails_to_check ); + + $customer = edd_get_customer_by( 'user_id', get_current_user_id() ); + + // If the current user has a customer record and the email address matches, we're good to go. + if ( ! empty( $customer->email ) && in_array( strtolower( $customer->email ), $emails_to_check, true ) ) { + return; + } + + $email_args = array( + 'email__in' => $emails_to_check, ); + if ( $customer ) { + $email_args['customer_id__not_in'] = array( $customer->id ); + } + $matching_emails = edd_get_customer_email_addresses( $email_args ); + if ( empty( $matching_emails ) ) { + return; + } - // 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'; + $existing_customer = false; + // Check if any of the matching emails belong to an existing customer. + foreach ( $matching_emails as $matching_email ) { + $email_customer = edd_get_customer( $matching_email->customer_id ); + if ( $email_customer && (int) $email_customer->user_id !== (int) $user->ID ) { + $existing_customer = true; + break; + } } - // 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 ); + if ( ! $existing_customer ) { + return; + } - // send info to the gateway for payment processing - edd_send_to_gateway( $valid_data['gateway'], $purchase_data ); - - exit; + edd_set_error( + 'edd-customer-email-exists', + /* translators: %s: email address */ + 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_in_user' => 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 +296,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 +352,909 @@ 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' ) ); + } + + // 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 $valid_user_data; -} + 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; + } + + $defaults = array( + 'user_login' => '', + 'user_pass' => '', + 'user_email' => '', + 'first_name' => isset( $user_data['user_first'] ) ? $user_data['user_first'] : '', + 'last_name' => isset( $user_data['user_last'] ) ? $user_data['user_last'] : '', + 'role' => get_option( 'default_role' ), ); - - // 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; -} + $user_args = wp_parse_args( $user_data, $defaults ); + $user_args = apply_filters( + 'edd_insert_user_args', + $user_args, + $user_data + ); + + // 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 ); + // Login new user. + $user = edd_log_user_in( $user_id, $user_data['user_login'], $user_data['user_pass'] ); + + // Allow themes and plugins to hook. + do_action( 'edd_insert_user', $user_id, $user_data ); + + // 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 - * - * Used to redirect a user back to the purchase - * page if there are errors present. + * Check the purchase to ensure a banned email is not allowed through * - * @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(); + if ( ! empty( $posted['edd_email'] ) ) { + $user_emails[] = $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'] ) && 'needs-to-login' === $posted['edd-purchase-var'] ) { + + // The user is logging in, check that their email is not banned. + $user_data = get_user_by( 'login', $posted['edd_user_login'] ); + if ( $user_data ) { + $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; + } + if ( edd_item_in_cart( $download_id ) ) { + $purchase_data = edd_get_purchase_session(); + } else { + $purchase_data = edd_build_straight_to_gateway_data( $download_id, $options, $quantity ); + edd_set_purchase_session( $purchase_data ); + } + + // If taxes are enabled and the download is not tax exclusive, send back to checkout. + if ( edd_use_taxes() && ! edd_download_is_tax_exclusive( $download_id ) ) { + edd_send_back_to_checkout(); + } + + $enabled_gateways = edd_get_enabled_payment_gateways(); + if ( empty( $purchase_data['gateway'] ) || ! array_key_exists( $purchase_data['gateway'], $enabled_gateways ) ) { + edd_send_back_to_checkout(); + } + + 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..f992977eca9 --- /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 = \EDD\Utils\Date::parse( $date, 'UTC' )->addDays( $refund_window ); + } else { + $date_refundable = \EDD\Utils\Date::parse( $date, 'UTC' )->addDays( $refund_window ); + } + + return $date_refundable->toDateTimeString(); +} 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_type() ), + 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, + '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(), + ), + 'hover' => array( + 'intersect' => false, + 'mode' => 'index', + ), + ); + + $default_xAxes = array( + array( + 'type' => 'time', + 'offset' => true, + 'display' => true, + 'gridLines' => array( + 'display' => false, + ), + 'ticks' => array( + 'source' => 'auto', + ), + 'position' => 'bottom', + 'time' => array( + 'unit' => $time_unit, + 'tooltipFormat' => $time_format, + ), + ), + ); + + $default_yAxes = array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'gridLines' => array( + 'drawBorder' => false, + ), + 'ticks' => array( + 'formattingType' => 'format', + 'beginAtZero' => true, + 'suggestedMin' => 0, + 'maxTicksLimit' => 5, + 'padding' => 10, + ), + 'fill' => false, + ), + ); + + // 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..89febf08da3 --- /dev/null +++ b/includes/reports/data/class-endpoint-view-registry.php @@ -0,0 +1,242 @@ +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 $key Item index to check. + */ + #[\ReturnTypeWillChange] + public function offsetUnset( $key ) {} + + /** + * 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..efa2913aba2 --- /dev/null +++ b/includes/reports/data/class-table-endpoint.php @@ -0,0 +1,225 @@ +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 ( class_exists( $display_args['class_name'] ) ) { + $this->set_list_table( $display_args['class_name'] ); + } elseif ( ! 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 ( \EDD\Utils\FileSystem::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..bc577d94097 --- /dev/null +++ b/includes/reports/data/customers/class-most-valuable-customers-list-table.php @@ -0,0 +1,157 @@ +prepare( " AND currency = %s ", $currency ); + } + + $sql = "SELECT customer_id, COUNT(id) AS order_count, SUM({$column}) AS total_spent + FROM {$wpdb->edd_orders} + WHERE status IN ('complete','revoked') 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, + $date_range['start']->format( 'mysql' ), + $date_range['end']->format( 'mysql' ) + ) + ); + + 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-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..37169bdb64f --- /dev/null +++ b/includes/reports/data/payment-gateways/class-gateway-stats-list-table.php @@ -0,0 +1,218 @@ + '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' ); + $order_status = Reports\get_filter_value( 'order_statuses' ); + + $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' => $order_status ? array( $order_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' => $order_status ? array( $order_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' => $order_status ? array( $order_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 The location of the table nav. + */ + protected function pagination( $which ) {} + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which The location of the table nav. + */ + 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..4dc5f822616 --- /dev/null +++ b/includes/reports/data/taxes/class-tax-collected-by-location-list-table.php @@ -0,0 +1,240 @@ + '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' ), + '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( $date_range['start']->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( $date_range['end']->format( 'mysql' ) ) ); + } + + $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, + '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' ), + '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() { + /** + * Filters the default report view. + * + * Due to the reports being registered later on, we cannot validate this, however, the reports do gracefully fail + * and redirect the user back to the `overview` report if someone supplies a bad value in the filter. + * + * @since 3.3.0 + * + * @param string $default_report_view The default report view. + */ + $default_report_view = apply_filters( 'edd_default_report_view', 'overview' ); + + return isset( $_REQUEST['view'] ) + ? sanitize_key( $_REQUEST['view'] ) + : $default_report_view; +} + +/** 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' + ), + 'order_statuses' => array( + 'label' => __( 'Order Statuses', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_order_status_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 date range. + * + * @since 3.3.0 + * + * @param string $default_range Default date range. + */ + $default_range = apply_filters( 'edd_reports_default_date_range', 'this_month' ); + + // If the default range is not valid, default to 'this_month'. + if ( ! array_key_exists( $default_range, get_dates_filter_options() ) ) { + $default_range = 'this_month'; + } + + /** + * Default relative date range. + * + * @since 3.3.0 + * + * @param string $default_relative_range Default relative date range. + */ + $default_relative_range = apply_filters( 'edd_reports_default_relative_date_range', 'previous_period' ); + + // If the default relative range is not valid, default to 'previous_period'. + if ( ! array_key_exists( $default_relative_range, get_relative_dates_filter_options() ) ) { + $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, null, true ); + + 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 )->startOfDay(), + 'end' => EDD()->utils->date( $end )->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 ) { + + + 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() { + // 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 'other': + $hour_by_hour = false; + $difference = ( $dates['end']->getTimestamp() - $dates['start']->getTimestamp() ); + if ( $difference <= ( DAY_IN_SECONDS * 2 ) ) { + $hour_by_hour = true; + } + break; + + case 'this_week': + case 'this_month': + case 'this_quarter': + case 'this_year': + 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, + 'status' => array( 'active', 'inactive', 'expired', 'archived' ), + ) ); + + $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( + /* translators: %s: Default currency of the store, in standard 3 character format (example: USD or EUR) */ + '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 + ) ); + ?> + + + + html->select( + array( + 'name' => 'order_statuses', + 'id' => 'edd_reports_filter_order_statuses', + 'options' => array_combine( + $statuses, + array_map( + function ( $status ) { + return edd_get_payment_status_label( $status ); + }, + $statuses + ) + ), + 'selected' => $status, + 'show_option_none' => false, + 'show_option_empty' => __( 'All order statuses', 'easy-digital-downloads' ), + ) + ); + ?> + + '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', + ) + ); + ?> + + + + + + $download_data['price_id'] ); + $price_name = edd_get_price_name( $download->ID, $args ); + if ( $price_name ) { + $download->post_title .= ': ' . $price_name; + } + } + + return esc_html( ' (' . $download->post_title . ')' ); +} diff --git a/includes/scripts.php b/includes/scripts.php index 2371e30ac1a..24e2d2825e7 100755 --- a/includes/scripts.php +++ b/includes/scripts.php @@ -2,161 +2,298 @@ /** * Scripts * - * @package Easy Digital Downloads - * @subpackage Scripts - * @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; // @codeCoverageIgnore + +/** Front End *****************************************************************/ + +/** + * Register all front-end scripts + * + * @since 3.0 + */ +function edd_register_scripts() { + EDD\Assets\Checkout::register(); +} +add_action( 'init', 'edd_register_scripts' ); + +/** + * Register styles + * + * Checks the styles option and hooks the required filter. + * + * @since 1.0 + */ +function edd_register_styles() { + EDD\Assets\Styles::register(); +} +add_action( 'init', 'edd_register_styles' ); /** * Load Scripts * * Enqueues the required scripts. * - * @access private - * @since 1.0 - * @return void -*/ - + * @since 1.0 + * @since 3.0 calls edd_enqueue_scripts() + */ function edd_load_scripts() { + edd_enqueue_scripts(); + edd_localize_scripts(); +} +add_action( 'wp_enqueue_scripts', 'edd_load_scripts' ); - global $edd_options, $post; +/** + * Load Scripts + * + * Enqueues the required scripts. + * + * @since 3.0 + */ +function edd_enqueue_scripts() { - wp_enqueue_script('jquery'); - - // Get position in cart of current download - if(isset($post->ID)) { - $position = edd_get_item_position_in_cart($post->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' - ) - ); + // Checkout scripts. + if ( edd_is_checkout() ) { + EDD\Assets\Checkout::enqueue(); } - - // 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)); + + // AJAX scripts, if enabled. + if ( ! edd_is_ajax_disabled() ) { + wp_enqueue_script( 'edd-ajax' ); } - wp_enqueue_script('edd-checkout-global', EDD_PLUGIN_URL . 'includes/js/edd-checkout-global.js'); } -add_action('wp_enqueue_scripts', 'edd_load_scripts'); - /** - * Register Styles + * Enqueue styles * * Checks the styles option and hooks the required filter. * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_register_styles() { - global $edd_options; - if(!isset($edd_options['disable_styles'])) { - wp_enqueue_style('edd-styles', EDD_PLUGIN_URL . 'includes/css/edd.css'); - } + * @since 3.0 + */ +function edd_enqueue_styles() { + wp_enqueue_style( 'edd-styles' ); } -add_action('wp_enqueue_scripts', 'edd_register_styles'); +add_action( 'wp_enqueue_scripts', 'edd_enqueue_styles' ); +/** + * Localize scripts + * + * @since 3.0 + * + * @global $post $post + */ +function edd_localize_scripts() { + EDD\Assets\Localization::checkout(); + EDD\Assets\Localization::ajax(); +} /** - * Load Admin Scripts + * Load head styles * - * Enqueues the required admin scripts. + * Ensures download styling is still shown correctly if a theme is using the CSS template file * - * @access private - * @since 1.0 - * @return void -*/ + * @since 2.5 + * @global $post + */ +function edd_load_head_styles() { + EDD\Assets\Styles::head(); +} +add_action( 'wp_print_styles', 'edd_load_head_styles' ); -function edd_load_admin_scripts($hook) { +/** + * Determine if the frontend scripts should be loaded in the footer or header (default: footer) + * + * @since 2.8.6 + * @return mixed + */ +function edd_scripts_in_footer() { + return apply_filters( 'edd_load_scripts_in_footer', true ); +} - global $post, $pagenow, $edd_discounts_page, $edd_payments_page, $edd_settings_page, $edd_reports_page, $edd_add_ons_page; +/** Admin Area ****************************************************************/ - $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' ) ); +/** + * Return the current script version + * + * @since 3.0 + * + * @return string + */ +function edd_admin_get_script_version() { + return edd_doing_script_debug() + ? time() + : EDD_VERSION; +} - if ( ! in_array( $hook, $edd_pages ) && ! is_object( $post ) ) - return; +/** + * Register all admin area scripts + * + * @since 3.0 + */ +function edd_register_admin_scripts() { + EDD\Admin\Assets\Scripts::register(); +} +add_action( 'admin_init', 'edd_register_admin_scripts' ); - 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'); - } - if($hook == 'download_page_edd-discounts') { - wp_enqueue_script('jquery-ui-datepicker'); - } - 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'); - } - 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_load_admin_scripts', 100); +/** + * Register all admin area styles + * + * @since 3.0 + */ +function edd_register_admin_styles() { + EDD\Admin\Assets\Styles::register(); +} +add_action( 'admin_init', 'edd_register_admin_styles' ); + +/** + * Print admin area scripts + * + * @since 3.0 + */ +function edd_enqueue_admin_scripts( $hook = '' ) { + EDD\Admin\Assets\Scripts::enqueue( $hook ); +} +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 = '' ) { + EDD\Admin\Assets\Styles::enqueue( $hook ); +} +add_action( 'admin_enqueue_scripts', 'edd_enqueue_admin_styles' ); +/** + * Localize all admin scripts + * + * @since 3.0 + */ +function edd_localize_admin_scripts() { + EDD\Admin\Assets\Localization::admin(); + EDD\Admin\Assets\Localization::upgrades(); +} +add_action( 'admin_enqueue_scripts', 'edd_localize_admin_scripts' ); /** * 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' ) ); + } + } + + $atts['text'] = sanitize_text_field( $atts['text'] ); + + 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_map( 'sanitize_html_class', 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(); + + $payment_key = false; + 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']; + } + + $order = edd_get_order_by( 'payment_key', $payment_key ); + if ( ! $order ) { + return '

    ' . $edd_receipt_args['error'] . '

    '; + } + + $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: username */ + $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..3565e812d1b --- a/includes/tax-functions.php +++ b/includes/tax-functions.php @@ -1,2 +1,562 @@ 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 = \EDD\Utils\Date::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 = $tax_rate; + $tax = 0.00; + + if ( edd_use_taxes() && $amount > 0 ) { + if ( is_null( $rate ) ) { + $rate = edd_get_tax_rate( $country, $region, $fallback ); + } + 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(); + + // Ensure the region is a string (CFM may pass an array). + $region = false; + if ( ! empty( $args['region'] ) ) { + $region = $args['region']; + if ( is_array( $region ) ) { + $region = reset( $region ); + } + } + + // 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( $region ) && ! empty( $tax_rate->description ) && 'region' === $tax_rate->scope ) { + if ( strtolower( $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..dcd1aeac83b 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,589 @@ 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; + if ( empty( $post_id ) && isset( $args['download_id'] ) ) { + $post_id = $args['download_id']; + } + + $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 ); + + $download = new EDD_Download( $args['download_id'] ); + + if( empty( $download->ID ) ) { + return false; + } + + // Product not published or user doesn't have permission to view drafts. + if ( 'publish' !== $download->post_status && ! current_user_can( 'edit_product', $download->ID ) ) { + return false; + } + + // Override the straight_to_gateway if the shop doesn't support it. + if ( ! edd_shop_supports_buy_now() || ! $download->supports_buy_now() ) { + $args['direct'] = false; + } + + $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="' . edd_format_amount( $data_price_value, true, '', 'data' ) . '"'; + $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 ]; + } + + /** + * Filter the purchase link arguments. + * + * @param array $args The purchase link arguments. + * @param EDD_Download $download The download object. Added in 3.2.4. + */ + $args = apply_filters( 'edd_purchase_link_args', $args, $download ); + + 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..7d97faaa76a --- /dev/null +++ b/includes/upgrades/functions.php @@ -0,0 +1,234 @@ + 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; + } + + $set_stripe_transients = true; + if ( ! empty( $edd_version ) ) { + update_option( 'edd_version_upgraded_from', $edd_version, false ); + + // Existing stores should set the upgraded version and the onboarding wizard as complete. + if ( ! get_option( 'edd_onboarding_completed', false ) && ! get_option( 'edd_onboarding_started', false ) ) { + update_option( 'edd_onboarding_completed', true, false ); + } + + // Stores upgrading from 3.2.0 or greater should not set the Stripe transients. + if ( version_compare( $edd_version, '3.2.0', '>=' ) ) { + $set_stripe_transients = false; + } + } + + /** + * If PayPal is connected, schedule a cron event to sync the webhooks in the background. + * + * @since 3.2.0 + */ + if ( EDD\Gateways\PayPal\has_rest_api_connection() && EDD\Gateways\PayPal\Webhooks\get_webhook_id() ) { + // Schedule a one time cron event to sync the webhooks. + \EDD\Cron\Events\SingleEvent::add( + time() + ( 5 * MINUTE_IN_SECONDS ), + 'edd_paypal_commerce_sync_webhooks' + ); + } + + /** + * If Stripe is active and EDD Pro or EDD Stripe is active, set a transient to check the license. + * + * @since 3.2.0 + */ + if ( $set_stripe_transients && edd_is_gateway_active( 'stripe' ) && ( edd_is_pro() || edds_is_pro() ) ) { + set_transient( 'edd_stripe_check_license', true, 30 ); + set_transient( 'edd_stripe_new_install', time(), HOUR_IN_SECONDS * 72 ); + } + + edd_update_db_version(); +} diff --git a/includes/user-functions.php b/includes/user-functions.php old mode 100644 new mode 100755 index c8e7e66ad55..26f65c501ce --- a/includes/user-functions.php +++ b/includes/user-functions.php @@ -4,146 +4,1152 @@ * * 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; + } + } + + // 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 ); + } + + 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 ( 'publish' === $status ) { + $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 ) ); +} + +/** + * Checks to see if a user has purchased a product. + * + * @since 1.0 + * @since 3.0 Refactored to be more efficient. + * + * @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. + * + * @return bool True if purchased, false otherwise. + */ +function edd_has_user_purchased( $user_id = 0, $downloads = array(), $variable_price_id = null ) { + global $wpdb; + + // Bail if no user ID passed. + if ( empty( $user_id ) ) { + return false; + } + + /** + * Fires before the queries execute. + * + * @since 2.7.7 + */ + do_action( 'edd_has_user_purchased_before', $user_id, $downloads, $variable_price_id ); + + if ( ! is_array( $downloads ) ) { + $downloads = array( $downloads ); + } + + // Bail if no downloads passed. + if ( empty( $downloads ) ) { + return false; + } + + $number = apply_filters( 'edd_users_purchased_products_payments', 9999999 ); + + $where_id = "'" . implode( "', '", $wpdb->_escape( $downloads ) ) . "'"; + $product_id = "oi.product_id IN ({$where_id})"; + + $price_id = isset( $variable_price_id ) + ? $wpdb->prepare( 'AND oi.price_id = %d', absint( $variable_price_id ) ) + : ''; + + $statuses = "'" . implode( "', '", $wpdb->_escape( edd_get_deliverable_order_item_statuses() ) ) . "'"; + $status_id = " AND oi.status IN({$statuses})"; + + // 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 + ); + + $result = (int) $wpdb->get_var( $sql ); + + $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; + } + + return (bool) edd_get_orders( + array( + 'user_id' => $user_id, + 'type' => 'sale', + 'status__in' => edd_get_complete_order_statuses(), + 'number' => 1, + ) + ); +} + + +/** + * 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; } - if($purchases) { - // return the download list - return $purchases; + + $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 ); } - - // no downloads - return false; + + 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; +} /** - * Has User Purchased + * Counts the total number of files a user (or customer if an email address is + * given) has downloaded * - * Checks to see if a user has purchased a download. + * @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. * - * @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 -*/ + * @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 ) || empty( $customer->email ) ) { + return; + } + + $user = get_user_by( 'email', $customer->email ); + + if ( ! $user instanceof WP_User ) { + return; + } + + $customer->update( array( 'user_id' => $user->ID ) ); -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 + // 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 ); - $users_purchases = edd_get_users_purchases($user_id); +/** + * 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 ) { + $customer = edd_get_customer_by( 'user_id', $user_id ); + if ( $customer ) { + edd_debug_log( 'User ID already exists for customer ID: ' . $customer->id ); + return; + } - $return = false; + $user = get_userdata( $user_id ); + if ( ! $user || is_wp_error( $user ) ) { + return; + } - if($users_purchases) { - foreach($users_purchases as $purchase) { + $customer = edd_get_customer_by( 'email', $user->user_email ); + if ( ! empty( $customer->user_id ) ) { + edd_debug_log( 'Customer already connected to a user ID: ' . $customer->user_id ); + return; + } - $purchase_meta = edd_get_payment_meta( $purchase->ID ); - $purchased_files = maybe_unserialize($purchase_meta['downloads']); + if ( $customer ) { + edd_update_customer( + $customer->id, + array( + 'user_id' => $user_id, + ) + ); + } +} +add_action( 'user_register', 'edd_connect_existing_customer_to_new_user', 10, 1 ); - if(is_array($purchased_files)) { +/** + * 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 ) { - foreach($purchased_files as $download) { + $email = get_the_author_meta( 'user_email', $user_id ); - if($download['id'] == $download_id) { + if ( empty( $email ) ) { + return; + } - if( !is_null( $variable_price_id ) && $variable_price_id !== false ) { + $payments = edd_get_payments( + array( + 's' => $email, + 'output' => 'payments', + ) + ); - if( $variable_price_id == $download['options']['price_id'] ) { - - return true; - - } else { - - $return = false; - - } + if ( $payments ) { - } else { - - $return = true; - - } + // 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 ); /** - * Has Purchases + * Counts the total number of customers. * - * Checks to see if a user has purchased at least one item. + * @since 1.7 + * @return int - The total number of customers. + */ +function edd_count_total_customers( $args = array() ) { + return edd_count_customers(); +} + + +/** + * Returns the saved address for a customer * - * @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. -*/ + * @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 ) { -function edd_has_purchases($user_id = null) { - - if(is_null($user_id)) { - global $user_ID; - $user_id = $user_ID; + // Maybe fall back to logged in user ID. + if ( empty( $user_id ) ) { + $user_id = get_current_user_id(); } - - if(edd_get_users_purchases($user_id)) { - return true; // user has at least one purchase + + $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, + ); + } + } + + return wp_parse_args( + $parsed_address, + array( + 'line1' => '', + 'line2' => '', + 'city' => '', + 'zip' => '', + 'country' => '', + 'state' => '', + ) + ); +} + +/** + * 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; + } + + 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 The User ID to send the email to. + */ +function edd_send_user_verification_email( $user_id = 0 ) { + + if ( empty( $user_id ) ) { + return; + } + + if ( ! edd_user_pending_verification( $user_id ) ) { + return; + } + $email = new EDD\Emails\Types\UserVerification( $user_id ); + $email->send(); +} + +/** + * 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'] ) && edd_get_user_verification_token( $url ) === $query_args['token'] ) { + + $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 + */ +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 ) { + // If we didn't get a user, bail. + if ( empty( $user ) ) { + 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 the store does not allow users to create API Keys, + * and the current user is not able to manage shop settings...bail. + */ + if ( ! edd_get_option( 'api_allow_user_keys', false ) && ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + // If the user is allowed to edit the current user, show the API Key fields. + if ( 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 ) ) { ?> +

    + +

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

    + +

    +

    + api->get_last_used( $user->ID ); + if ( empty( $last_used ) ) { + esc_html_e( 'Never Used', 'easy-digital-downloads' ); + } else { + // Convert the date to the store timezone. + $converted_date = EDD()->utils->date( $last_used, null, true )->toDateTimeString(); + + // Use the UTC time for the human time diff. + $date_diff = human_time_diff( strtotime( $last_used ) ); + + // Build the date diff string. + $date_diff_string = sprintf( + /* translators: %s: a length of time (e.g. "1 second") */ + __( '%s ago', 'easy-digital-downloads' ), + $date_diff + ); + echo '' . __( 'Last Used:', 'easy-digital-downloads' ) . ' '; + echo ''; + } + ?> +

    +
    + +
    + + + + + + + + + +
    + 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..babdc984b36 --- /dev/null +++ b/includes/users/login.php @@ -0,0 +1,247 @@ +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, + ); + + /** + * Fires before a user is logged in. + * + * This can be useful for performing actions prior to the user being logged in. Since EDD sparingly logs users in, + * it's important to note that this action is not used in most normal WordPress operations, but primarily during checkout, + * and with the Auto Register functions. + * + * @since 3.2.8 + * + * @param int $user_id The user ID. + * @param string $user_login The user login. + */ + do_action( 'edd_pre_log_user_in', $user_id, $user_login ); + + $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..d549a384874 --- /dev/null +++ b/includes/users/lost-password.php @@ -0,0 +1,307 @@ +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 The user object. + * @return false|string + */ +function edd_get_password_reset_link( $user ) { + $key = get_password_reset_key( $user ); + if ( is_wp_error( $key ) ) { + return false; + } + $args = array( + 'action' => 'rp', + 'key' => rawurlencode( $key ), + 'login' => rawurlencode( $user->user_login ), + ); + if ( edd_get_login_page_uri() ) { + $args['edd_action'] = 'password_reset_requested'; + } + + return add_query_arg( + $args, + 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; + } + $email = edd_get_email( 'password_reset' ); + if ( ! $email ) { + return $message; + } + $message = $email->content; + if ( false === strpos( $message, '{password_reset_link}' ) ) { + $message = $email->get_template()->get_default( 'content' ); + } + $message = str_replace( + '{password_reset_link}', + add_query_arg( + array( + 'edd_action' => 'password_reset_requested', + 'key' => $key, + 'login' => rawurlencode( $user_login ), + ), + esc_url_raw( $_POST['edd_redirect'] ) + ), + $message + ); + + return edd_do_email_tags( $message, $user_data->ID, $user_data, 'user' ); +} + +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' ) ); + } + + $user = false; + if ( empty( $data['rp_key'] ) ) { + edd_set_error( 'password_reset_failed', __( 'Invalid password reset request.', 'easy-digital-downloads' ) ); + } elseif ( ! empty( $data['user_login'] ) ) { + $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'] ) ) { + $data['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 = !empty( $data['user_login'] ) ? get_user_by( 'login', $data['user_login'] ) : false; + if ( false === $user ) { + edd_set_error( 'password_reset_unsuccessful', __( 'Your password could not be reset.', 'easy-digital-downloads' ) ); + } + + if ( empty( $data['edd_redirect'] ) ) { + $redirect = edd_get_login_page_uri() ?: home_url(); // phpcs:ignore Universal.Operators.DisallowShortTernary.Found + edd_set_error( 'password_reset_unsuccessful', __( 'Your password could not be reset.', 'easy-digital-downloads' ) ); + } else { + $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' ) ); + 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..558dd8c7b5e --- /dev/null +++ b/includes/users/register.php @@ -0,0 +1,130 @@ + $data['edd_user_email'], + 'user_id__not_in' => array( null ), + ) + ); + if ( ( ! empty( $data['edd_user_email'] ) && 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-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/widgets.php b/includes/widgets.php index e0d2b85f7a2..11bd19f2ccb 100755 --- a/includes/widgets.php +++ b/includes/widgets.php @@ -2,13 +2,17 @@ /** * Widgets * - * @package Easy Digital Downloads + * Widgets related funtions and widget registration. + * + * @package EDD * @subpackage Widgets - * @copyright Copyright (c) 2012, Pippin Williamson + * @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; /* |-------------------------------------------------------------------------- @@ -17,333 +21,477 @@ | | - Cart Widget | - Categories / Tags Widget -| - Purchase History Widget | */ /** - * Cart Widget + * Cart Widget. * * Downloads cart widget class. * - * @access private - * @since 1.0 - * @return void + * @since 1.0 + * @return void */ - class edd_cart_widget extends WP_Widget { + /** Constructor */ + function __construct() { + parent::__construct( 'edd_cart_widget', __( 'Downloads Cart', 'easy-digital-downloads' ), array( 'description' => __( '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..3e444946dad --- /dev/null +++ b/languages/easy-digital-downloads.pot @@ -0,0 +1,23042 @@ +# Copyright (C) 2025 Easy Digital Downloads +# This file is distributed under the same license as the Easy Digital Downloads plugin. +msgid "" +msgstr "" +"Project-Id-Version: Easy Digital Downloads 3.3.7\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/easy-digital-downloads-public\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: 2025-03-17T21:31:18+00:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.11.0\n" + +#. Plugin Name of the plugin +#. Author of the plugin +#: easy-digital-downloads.php +#: includes/blocks/includes/functions.php:27 +#: src/Admin/Onboarding/Steps/Tools.php:152 +#: src/Admin/SiteHealth/Test.php:21 +msgid "Easy Digital Downloads" +msgstr "" + +#. Plugin URI of the plugin +#. Author URI of the plugin +#: easy-digital-downloads.php +msgid "https://easydigitaldownloads.com" +msgstr "" + +#. Description of the plugin +#: easy-digital-downloads.php +msgid "The easiest way to sell digital products with WordPress." +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 "Afghanistan" +msgstr "" + +#: i18n/countries.php:8 +msgid "Åland Islands" +msgstr "" + +#: i18n/countries.php:9 +msgid "Albania" +msgstr "" + +#: i18n/countries.php:10 +msgid "Algeria" +msgstr "" + +#: i18n/countries.php:11 +#: i18n/states-us.php:58 +msgid "American Samoa" +msgstr "" + +#: i18n/countries.php:12 +msgid "Andorra" +msgstr "" + +#: i18n/countries.php:13 +msgid "Angola" +msgstr "" + +#: i18n/countries.php:14 +msgid "Anguilla" +msgstr "" + +#: i18n/countries.php:15 +msgid "Antarctica" +msgstr "" + +#: i18n/countries.php:16 +msgid "Antigua and Barbuda" +msgstr "" + +#: i18n/countries.php:17 +msgid "Argentina" +msgstr "" + +#: i18n/countries.php:18 +msgid "Armenia" +msgstr "" + +#: i18n/countries.php:19 +msgid "Aruba" +msgstr "" + +#: i18n/countries.php:20 +msgid "Australia" +msgstr "" + +#: i18n/countries.php:21 +msgid "Austria" +msgstr "" + +#: i18n/countries.php:22 +msgid "Azerbaijan" +msgstr "" + +#: i18n/countries.php:23 +msgid "Bahamas" +msgstr "" + +#: i18n/countries.php:24 +msgid "Bahrain" +msgstr "" + +#: i18n/countries.php:25 +msgid "Bangladesh" +msgstr "" + +#: i18n/countries.php:26 +msgid "Barbados" +msgstr "" + +#: i18n/countries.php:27 +msgid "Belarus" +msgstr "" + +#: i18n/countries.php:28 +msgid "Belgium" +msgstr "" + +#: i18n/countries.php:29 +msgid "Belize" +msgstr "" + +#: i18n/countries.php:30 +msgid "Benin" +msgstr "" + +#: i18n/countries.php:31 +msgid "Bermuda" +msgstr "" + +#: i18n/countries.php:32 +msgid "Bhutan" +msgstr "" + +#: i18n/countries.php:33 +msgid "Bolivia" +msgstr "" + +#: i18n/countries.php:34 +msgid "Bonaire, Saint Eustatius and Saba" +msgstr "" + +#: i18n/countries.php:35 +msgid "Bosnia and Herzegovina" +msgstr "" + +#: i18n/countries.php:36 +msgid "Botswana" +msgstr "" + +#: i18n/countries.php:37 +msgid "Bouvet Island" +msgstr "" + +#: i18n/countries.php:38 +msgid "Brazil" +msgstr "" + +#: i18n/countries.php:39 +msgid "British Indian Ocean Territory" +msgstr "" + +#: i18n/countries.php:40 +msgid "Brunei Darrussalam" +msgstr "" + +#: i18n/countries.php:41 +msgid "Bulgaria" +msgstr "" + +#: i18n/countries.php:42 +msgid "Burkina Faso" +msgstr "" + +#: i18n/countries.php:43 +msgid "Burundi" +msgstr "" + +#: i18n/countries.php:44 +msgid "Cambodia" +msgstr "" + +#: i18n/countries.php:45 +msgid "Cameroon" +msgstr "" + +#: i18n/countries.php:46 +msgid "Canada" +msgstr "" + +#: i18n/countries.php:47 +msgid "Cape Verde" +msgstr "" + +#: i18n/countries.php:48 +msgid "Cayman Islands" +msgstr "" + +#: i18n/countries.php:49 +msgid "Central African Republic" +msgstr "" + +#: i18n/countries.php:50 +msgid "Chad" +msgstr "" + +#: i18n/countries.php:51 +msgid "Chile" +msgstr "" + +#: i18n/countries.php:52 +msgid "China" +msgstr "" + +#: i18n/countries.php:53 +msgid "Christmas Island" +msgstr "" + +#: i18n/countries.php:54 +msgid "Cocos Islands" +msgstr "" + +#: i18n/countries.php:55 +msgid "Colombia" +msgstr "" + +#: i18n/countries.php:56 +msgid "Comoros" +msgstr "" + +#: i18n/countries.php:57 +msgid "Congo, Democratic People's Republic" +msgstr "" + +#: i18n/countries.php:58 +msgid "Congo, Republic of" +msgstr "" + +#: i18n/countries.php:59 +msgid "Cook Islands" +msgstr "" + +#: i18n/countries.php:60 +msgid "Costa Rica" +msgstr "" + +#: i18n/countries.php:61 +msgid "Cote d'Ivoire" +msgstr "" + +#: i18n/countries.php:62 +msgid "Croatia/Hrvatska" +msgstr "" + +#: i18n/countries.php:63 +msgid "Cuba" +msgstr "" + +#: i18n/countries.php:64 +msgid "CuraÇao" +msgstr "" + +#: i18n/countries.php:65 +msgid "Cyprus" +msgstr "" + +#: i18n/countries.php:66 +msgid "Czechia" +msgstr "" + +#: i18n/countries.php:67 +msgid "Denmark" +msgstr "" + +#: i18n/countries.php:68 +msgid "Djibouti" +msgstr "" + +#: i18n/countries.php:69 +msgid "Dominica" +msgstr "" + +#: i18n/countries.php:70 +msgid "Dominican Republic" +msgstr "" + +#: i18n/countries.php:71 +msgid "East Timor" +msgstr "" + +#: i18n/countries.php:72 +msgid "Ecuador" +msgstr "" + +#: i18n/countries.php:73 +msgid "Egypt" +msgstr "" + +#: i18n/countries.php:74 +msgid "Equatorial Guinea" +msgstr "" + +#: i18n/countries.php:75 +msgid "El Salvador" +msgstr "" + +#: i18n/countries.php:76 +msgid "Eritrea" +msgstr "" + +#: i18n/countries.php:77 +msgid "Estonia" +msgstr "" + +#: i18n/countries.php:78 +msgid "Ethiopia" +msgstr "" + +#: i18n/countries.php:79 +msgid "Falkland Islands" +msgstr "" + +#: i18n/countries.php:80 +msgid "Faroe Islands" +msgstr "" + +#: i18n/countries.php:81 +msgid "Fiji" +msgstr "" + +#: i18n/countries.php:82 +msgid "Finland" +msgstr "" + +#: i18n/countries.php:83 +msgid "France" +msgstr "" + +#: i18n/countries.php:84 +msgid "French Guiana" +msgstr "" + +#: i18n/countries.php:85 +msgid "French Polynesia" +msgstr "" + +#: i18n/countries.php:86 +msgid "French Southern Territories" +msgstr "" + +#: i18n/countries.php:87 +msgid "Gabon" +msgstr "" + +#: i18n/countries.php:88 +msgid "Gambia" +msgstr "" + +#: i18n/countries.php:89 +#: i18n/states-us.php:17 +msgid "Georgia" +msgstr "" + +#: i18n/countries.php:90 +msgid "Germany" +msgstr "" + +#: i18n/countries.php:91 +msgid "Greece" +msgstr "" + +#: i18n/countries.php:92 +msgid "Ghana" +msgstr "" + +#: i18n/countries.php:93 +msgid "Gibraltar" +msgstr "" + +#: i18n/countries.php:94 +msgid "Greenland" +msgstr "" + +#: i18n/countries.php:95 +msgid "Grenada" +msgstr "" + +#: i18n/countries.php:96 +msgid "Guadeloupe" +msgstr "" + +#: i18n/countries.php:97 +#: i18n/states-us.php:62 +msgid "Guam" +msgstr "" + +#: i18n/countries.php:98 +msgid "Guatemala" +msgstr "" + +#: i18n/countries.php:99 +msgid "Guernsey" +msgstr "" + +#: i18n/countries.php:100 +msgid "Guinea" +msgstr "" + +#: i18n/countries.php:101 +msgid "Guinea-Bissau" +msgstr "" + +#: i18n/countries.php:102 +msgid "Guyana" +msgstr "" + +#: i18n/countries.php:103 +msgid "Haiti" +msgstr "" + +#: i18n/countries.php:104 +msgid "Heard and McDonald Islands" +msgstr "" + +#: i18n/countries.php:105 +msgid "Holy See (City Vatican State)" +msgstr "" + +#: i18n/countries.php:106 +msgid "Honduras" +msgstr "" + +#: i18n/countries.php:107 +msgid "Hong Kong" +msgstr "" + +#: i18n/countries.php:108 +msgid "Hungary" +msgstr "" + +#: i18n/countries.php:109 +msgid "Iceland" +msgstr "" + +#: i18n/countries.php:110 +msgid "India" +msgstr "" + +#: i18n/countries.php:111 +msgid "Indonesia" +msgstr "" + +#: i18n/countries.php:112 +msgid "Iran" +msgstr "" + +#: i18n/countries.php:113 +msgid "Iraq" +msgstr "" + +#: i18n/countries.php:114 +msgid "Ireland" +msgstr "" + +#: i18n/countries.php:115 +msgid "Isle of Man" +msgstr "" + +#: i18n/countries.php:116 +msgid "Israel" +msgstr "" + +#: i18n/countries.php:117 +msgid "Italy" +msgstr "" + +#: i18n/countries.php:118 +msgid "Jamaica" +msgstr "" + +#: i18n/countries.php:119 +msgid "Japan" +msgstr "" + +#: i18n/countries.php:120 +msgid "Jersey" +msgstr "" + +#: i18n/countries.php:121 +msgid "Jordan" +msgstr "" + +#: i18n/countries.php:122 +msgid "Kazakhstan" +msgstr "" + +#: i18n/countries.php:123 +msgid "Kenya" +msgstr "" + +#: i18n/countries.php:124 +msgid "Kiribati" +msgstr "" + +#: i18n/countries.php:125 +msgid "Kuwait" +msgstr "" + +#: i18n/countries.php:126 +msgid "Kyrgyzstan" +msgstr "" + +#: i18n/countries.php:127 +msgid "Lao People's Democratic Republic" +msgstr "" + +#: i18n/countries.php:128 +msgid "Latvia" +msgstr "" + +#: i18n/countries.php:129 +msgid "Lebanon" +msgstr "" + +#: i18n/countries.php:130 +msgid "Lesotho" +msgstr "" + +#: i18n/countries.php:131 +msgid "Liberia" +msgstr "" + +#: i18n/countries.php:132 +msgid "Libyan Arab Jamahiriya" +msgstr "" + +#: i18n/countries.php:133 +msgid "Liechtenstein" +msgstr "" + +#: i18n/countries.php:134 +msgid "Lithuania" +msgstr "" + +#: i18n/countries.php:135 +msgid "Luxembourg" +msgstr "" + +#: i18n/countries.php:136 +msgid "Macau" +msgstr "" + +#: i18n/countries.php:137 +msgid "Macedonia" +msgstr "" + +#: i18n/countries.php:138 +msgid "Madagascar" +msgstr "" + +#: i18n/countries.php:139 +msgid "Malawi" +msgstr "" + +#: i18n/countries.php:140 +msgid "Malaysia" +msgstr "" + +#: i18n/countries.php:141 +msgid "Maldives" +msgstr "" + +#: i18n/countries.php:142 +msgid "Mali" +msgstr "" + +#: i18n/countries.php:143 +msgid "Malta" +msgstr "" + +#: i18n/countries.php:144 +#: i18n/states-us.php:63 +msgid "Marshall Islands" +msgstr "" + +#: i18n/countries.php:145 +msgid "Martinique" +msgstr "" + +#: i18n/countries.php:146 +msgid "Mauritania" +msgstr "" + +#: i18n/countries.php:147 +msgid "Mauritius" +msgstr "" + +#: i18n/countries.php:148 +msgid "Mayotte" +msgstr "" + +#: i18n/countries.php:149 +msgid "Mexico" +msgstr "" + +#: i18n/countries.php:150 +msgid "Micronesia" +msgstr "" + +#: i18n/countries.php:151 +msgid "Moldova, Republic of" +msgstr "" + +#: i18n/countries.php:152 +msgid "Monaco" +msgstr "" + +#: i18n/countries.php:153 +msgid "Mongolia" +msgstr "" + +#: i18n/countries.php:154 +msgid "Montenegro" +msgstr "" + +#: i18n/countries.php:155 +msgid "Montserrat" +msgstr "" + +#: i18n/countries.php:156 +msgid "Morocco" +msgstr "" + +#: i18n/countries.php:157 +msgid "Mozambique" +msgstr "" + +#: i18n/countries.php:158 +msgid "Myanmar" +msgstr "" + +#: i18n/countries.php:159 +msgid "Namibia" +msgstr "" + +#: i18n/countries.php:160 +msgid "Nauru" +msgstr "" + +#: i18n/countries.php:161 +msgid "Nepal" +msgstr "" + +#: i18n/countries.php:162 +msgid "Netherlands" +msgstr "" + +#: i18n/countries.php:163 +msgid "Netherlands Antilles" +msgstr "" + +#: i18n/countries.php:164 +msgid "New Caledonia" +msgstr "" + +#: i18n/countries.php:165 +msgid "New Zealand" +msgstr "" + +#: i18n/countries.php:166 +msgid "Nicaragua" +msgstr "" + +#: i18n/countries.php:167 +msgid "Niger" +msgstr "" + +#: i18n/countries.php:168 +msgid "Nigeria" +msgstr "" + +#: i18n/countries.php:169 +msgid "Niue" +msgstr "" + +#: i18n/countries.php:170 +msgid "Norfolk Island" +msgstr "" + +#: i18n/countries.php:171 +msgid "North Korea" +msgstr "" + +#: i18n/countries.php:172 +#: i18n/states-us.php:64 +msgid "Northern Mariana Islands" +msgstr "" + +#: i18n/countries.php:173 +msgid "Norway" +msgstr "" + +#: i18n/countries.php:174 +msgid "Oman" +msgstr "" + +#: i18n/countries.php:175 +msgid "Pakistan" +msgstr "" + +#: i18n/countries.php:176 +#: i18n/states-us.php:65 +msgid "Palau" +msgstr "" + +#: i18n/countries.php:177 +msgid "Palestinian Territories" +msgstr "" + +#: i18n/countries.php:178 +msgid "Panama" +msgstr "" + +#: i18n/countries.php:179 +msgid "Papua New Guinea" +msgstr "" + +#: i18n/countries.php:180 +msgid "Paraguay" +msgstr "" + +#: i18n/countries.php:181 +msgid "Peru" +msgstr "" + +#: i18n/countries.php:182 +msgid "Philippines" +msgstr "" + +#: i18n/countries.php:183 +msgid "Pitcairn Island" +msgstr "" + +#: i18n/countries.php:184 +msgid "Poland" +msgstr "" + +#: i18n/countries.php:185 +msgid "Portugal" +msgstr "" + +#: i18n/countries.php:186 +#: i18n/states-us.php:67 +msgid "Puerto Rico" +msgstr "" + +#: i18n/countries.php:187 +msgid "Qatar" +msgstr "" + +#: i18n/countries.php:188 +msgid "Republic of Kosovo" +msgstr "" + +#: i18n/countries.php:189 +msgid "Reunion Island" +msgstr "" + +#: i18n/countries.php:190 +msgid "Romania" +msgstr "" + +#: i18n/countries.php:191 +msgid "Russian Federation" +msgstr "" + +#: i18n/countries.php:192 +msgid "Rwanda" +msgstr "" + +#: i18n/countries.php:193 +msgid "Saint Barthélemy" +msgstr "" + +#: i18n/countries.php:194 +msgid "Saint Helena" +msgstr "" + +#: i18n/countries.php:195 +msgid "Saint Kitts and Nevis" +msgstr "" + +#: i18n/countries.php:196 +msgid "Saint Lucia" +msgstr "" + +#: i18n/countries.php:197 +msgid "Saint Martin (French)" +msgstr "" + +#: i18n/countries.php:198 +msgid "Saint Martin (Dutch)" +msgstr "" + +#: i18n/countries.php:199 +msgid "Saint Pierre and Miquelon" +msgstr "" + +#: i18n/countries.php:200 +msgid "Saint Vincent and the Grenadines" +msgstr "" + +#: i18n/countries.php:201 +msgid "San Marino" +msgstr "" + +#: i18n/countries.php:202 +msgid "São Tomé and Príncipe" +msgstr "" + +#: i18n/countries.php:203 +msgid "Saudi Arabia" +msgstr "" + +#: i18n/countries.php:204 +msgid "Senegal" +msgstr "" + +#: i18n/countries.php:205 +msgid "Serbia" +msgstr "" + +#: i18n/countries.php:206 +msgid "Seychelles" +msgstr "" + +#: i18n/countries.php:207 +msgid "Sierra Leone" +msgstr "" + +#: i18n/countries.php:208 +msgid "Singapore" +msgstr "" + +#: i18n/countries.php:209 +msgid "Slovak Republic" +msgstr "" + +#: i18n/countries.php:210 +msgid "Slovenia" +msgstr "" + +#: i18n/countries.php:211 +msgid "Solomon Islands" +msgstr "" + +#: i18n/countries.php:212 +msgid "Somalia" +msgstr "" + +#: i18n/countries.php:213 +msgid "South Africa" +msgstr "" + +#: i18n/countries.php:214 +msgid "South Georgia" +msgstr "" + +#: i18n/countries.php:215 +msgid "South Korea" +msgstr "" + +#: i18n/countries.php:216 +msgid "South Sudan" +msgstr "" + +#: i18n/countries.php:217 +msgid "Spain" +msgstr "" + +#: i18n/countries.php:218 +msgid "Sri Lanka" +msgstr "" + +#: i18n/countries.php:219 +msgid "Sudan" +msgstr "" + +#: i18n/countries.php:220 +msgid "Suriname" +msgstr "" + +#: i18n/countries.php:221 +msgid "Svalbard and Jan Mayen Islands" +msgstr "" + +#: i18n/countries.php:222 +msgid "Swaziland" +msgstr "" + +#: i18n/countries.php:223 +msgid "Sweden" +msgstr "" + +#: i18n/countries.php:224 +msgid "Switzerland" +msgstr "" + +#: i18n/countries.php:225 +msgid "Syrian Arab Republic" +msgstr "" + +#: i18n/countries.php:226 +msgid "Taiwan" +msgstr "" + +#: i18n/countries.php:227 +msgid "Tajikistan" +msgstr "" + +#: i18n/countries.php:228 +msgid "Tanzania" +msgstr "" + +#: i18n/countries.php:229 +msgid "Thailand" +msgstr "" + +#: i18n/countries.php:230 +msgid "Timor-Leste" +msgstr "" + +#: i18n/countries.php:231 +msgid "Togo" +msgstr "" + +#: i18n/countries.php:232 +msgid "Tokelau" +msgstr "" + +#: i18n/countries.php:233 +msgid "Tonga" +msgstr "" + +#: i18n/countries.php:234 +msgid "Trinidad and Tobago" +msgstr "" + +#: i18n/countries.php:235 +msgid "Tunisia" +msgstr "" + +#: i18n/countries.php:236 +msgid "Turkey" +msgstr "" + +#: i18n/countries.php:237 +msgid "Turkmenistan" +msgstr "" + +#: i18n/countries.php:238 +msgid "Turks and Caicos Islands" +msgstr "" + +#: i18n/countries.php:239 +msgid "Tuvalu" +msgstr "" + +#: i18n/countries.php:240 +msgid "Uganda" +msgstr "" + +#: i18n/countries.php:241 +msgid "Ukraine" +msgstr "" + +#: i18n/countries.php:242 +msgid "United Arab Emirates" +msgstr "" + +#: i18n/countries.php:243 +msgid "United Kingdom" +msgstr "" + +#: i18n/countries.php:244 +msgid "United States" +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-bj.php:14 +msgid "Alibori" +msgstr "" + +#: i18n/states-bj.php:15 +msgid "Atakora" +msgstr "" + +#: i18n/states-bj.php:16 +msgid "Atlantique" +msgstr "" + +#: i18n/states-bj.php:17 +msgid "Borgou" +msgstr "" + +#: i18n/states-bj.php:18 +msgid "Collines" +msgstr "" + +#: i18n/states-bj.php:19 +msgid "Kouffo" +msgstr "" + +#: i18n/states-bj.php:20 +msgid "Donga" +msgstr "" + +#: i18n/states-bj.php:21 +msgid "Littoral" +msgstr "" + +#: i18n/states-bj.php:22 +msgid "Mono" +msgstr "" + +#: i18n/states-bj.php:23 +msgid "Ouémé" +msgstr "" + +#: i18n/states-bj.php:24 +msgid "Plateau" +msgstr "" + +#: i18n/states-bj.php:25 +msgid "Zou" +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-legacy.php:6 +msgid "Aberdeen City" +msgstr "" + +#: i18n/states-gb-legacy.php:7 +msgid "Antrim and Newtownabbey" +msgstr "" + +#: i18n/states-gb-legacy.php:8 +msgid "Ards and North Down" +msgstr "" + +#: i18n/states-gb-legacy.php:9 +msgid "Argyll and Bute" +msgstr "" + +#: i18n/states-gb-legacy.php:10 +msgid "Armagh, Banbridge and Craigavon" +msgstr "" + +#: i18n/states-gb-legacy.php:11 +msgid "Barking and Dagenham" +msgstr "" + +#: i18n/states-gb-legacy.php:12 +msgid "Barnet" +msgstr "" + +#: i18n/states-gb-legacy.php:13 +msgid "Barnsley" +msgstr "" + +#: i18n/states-gb-legacy.php:14 +msgid "Bath and North East Somerset" +msgstr "" + +#: i18n/states-gb-legacy.php:15 +msgid "Bedford" +msgstr "" + +#: i18n/states-gb-legacy.php:16 +msgid "Belfast" +msgstr "" + +#: i18n/states-gb-legacy.php:17 +msgid "Bexley" +msgstr "" + +#: i18n/states-gb-legacy.php:18 +msgid "Birmingham" +msgstr "" + +#: i18n/states-gb-legacy.php:19 +msgid "Blackburn with Darwen" +msgstr "" + +#: i18n/states-gb-legacy.php:20 +msgid "Blackpool" +msgstr "" + +#: i18n/states-gb-legacy.php:21 +msgid "Bolton" +msgstr "" + +#: i18n/states-gb-legacy.php:22 +msgid "Bournemouth" +msgstr "" + +#: i18n/states-gb-legacy.php:23 +msgid "Bracknell Forest" +msgstr "" + +#: i18n/states-gb-legacy.php:24 +msgid "Bradford" +msgstr "" + +#: i18n/states-gb-legacy.php:25 +msgid "Brent" +msgstr "" + +#: i18n/states-gb-legacy.php:26 +msgid "Brighton and Hove" +msgstr "" + +#: i18n/states-gb-legacy.php:27 +msgid "Bristol, City of" +msgstr "" + +#: i18n/states-gb-legacy.php:28 +msgid "Bromley" +msgstr "" + +#: i18n/states-gb-legacy.php:29 +msgid "Bury" +msgstr "" + +#: i18n/states-gb-legacy.php:30 +msgid "Calderdale" +msgstr "" + +#: i18n/states-gb-legacy.php:31 +msgid "Camden" +msgstr "" + +#: i18n/states-gb-legacy.php:32 +msgid "Causeway Coast and Glens" +msgstr "" + +#: i18n/states-gb-legacy.php:33 +msgid "Central Bedfordshire" +msgstr "" + +#: i18n/states-gb-legacy.php:34 +msgid "Cheshire East" +msgstr "" + +#: i18n/states-gb-legacy.php:35 +msgid "Cheshire West and Chester" +msgstr "" + +#: i18n/states-gb-legacy.php:36 +msgid "Coventry" +msgstr "" + +#: i18n/states-gb-legacy.php:37 +msgid "Croydon" +msgstr "" + +#: i18n/states-gb-legacy.php:38 +msgid "Darlington" +msgstr "" + +#: i18n/states-gb-legacy.php:39 +msgid "Derby" +msgstr "" + +#: i18n/states-gb-legacy.php:40 +msgid "Derry and Strabane" +msgstr "" + +#: i18n/states-gb-legacy.php:41 +msgid "Doncaster" +msgstr "" + +#: i18n/states-gb-legacy.php:42 +msgid "Dudley" +msgstr "" + +#: i18n/states-gb-legacy.php:43 +msgid "Dundee City" +msgstr "" + +#: i18n/states-gb-legacy.php:44 +msgid "Durham, County" +msgstr "" + +#: i18n/states-gb-legacy.php:45 +msgid "Ealing" +msgstr "" + +#: i18n/states-gb-legacy.php:46 +msgid "Edinburgh, City of" +msgstr "" + +#: i18n/states-gb-legacy.php:47 +msgid "Eilean Siar" +msgstr "" + +#: i18n/states-gb-legacy.php:48 +msgid "Enfield" +msgstr "" + +#: i18n/states-gb-legacy.php:49 +msgid "Falkirk" +msgstr "" + +#: i18n/states-gb-legacy.php:50 +msgid "Fermanagh and Omagh" +msgstr "" + +#: i18n/states-gb-legacy.php:51 +msgid "Gateshead" +msgstr "" + +#: i18n/states-gb-legacy.php:52 +msgid "Glasgow City" +msgstr "" + +#: i18n/states-gb-legacy.php:53 +msgid "Greenwich" +msgstr "" + +#: i18n/states-gb-legacy.php:54 +msgid "Hackney" +msgstr "" + +#: i18n/states-gb-legacy.php:55 +msgid "Halton" +msgstr "" + +#: i18n/states-gb-legacy.php:56 +msgid "Hammersmith and Fulham" +msgstr "" + +#: i18n/states-gb-legacy.php:57 +msgid "Haringey" +msgstr "" + +#: i18n/states-gb-legacy.php:58 +msgid "Harrow" +msgstr "" + +#: i18n/states-gb-legacy.php:59 +msgid "Hartlepool" +msgstr "" + +#: i18n/states-gb-legacy.php:60 +msgid "Havering" +msgstr "" + +#: i18n/states-gb-legacy.php:61 +msgid "Hillingdon" +msgstr "" + +#: i18n/states-gb-legacy.php:62 +msgid "Hounslow" +msgstr "" + +#: i18n/states-gb-legacy.php:63 +msgid "Isles of Scilly" +msgstr "" + +#: i18n/states-gb-legacy.php:64 +msgid "Islington" +msgstr "" + +#: i18n/states-gb-legacy.php:65 +msgid "Kensington and Chelsea" +msgstr "" + +#: i18n/states-gb-legacy.php:66 +msgid "Kingston upon Hull" +msgstr "" + +#: i18n/states-gb-legacy.php:67 +msgid "Kingston upon Thames" +msgstr "" + +#: i18n/states-gb-legacy.php:68 +msgid "Kirklees" +msgstr "" + +#: i18n/states-gb-legacy.php:69 +msgid "Knowsley" +msgstr "" + +#: i18n/states-gb-legacy.php:70 +msgid "Lambeth" +msgstr "" + +#: i18n/states-gb-legacy.php:71 +msgid "Leeds" +msgstr "" + +#: i18n/states-gb-legacy.php:72 +msgid "Lewisham" +msgstr "" + +#: i18n/states-gb-legacy.php:73 +msgid "Lisburn and Castlereagh" +msgstr "" + +#: i18n/states-gb-legacy.php:74 +msgid "Liverpool" +msgstr "" + +#: i18n/states-gb-legacy.php:75 +msgid "London, City of" +msgstr "" + +#: i18n/states-gb-legacy.php:76 +msgid "Luton" +msgstr "" + +#: i18n/states-gb-legacy.php:77 +msgid "Manchester" +msgstr "" + +#: i18n/states-gb-legacy.php:78 +msgid "Medway" +msgstr "" + +#: i18n/states-gb-legacy.php:79 +msgid "Merton" +msgstr "" + +#: i18n/states-gb-legacy.php:80 +msgid "Mid and East Antrim" +msgstr "" + +#: i18n/states-gb-legacy.php:81 +msgid "Mid Ulster" +msgstr "" + +#: i18n/states-gb-legacy.php:82 +msgid "Middlesbrough" +msgstr "" + +#: i18n/states-gb-legacy.php:83 +msgid "Milton Keynes" +msgstr "" + +#: i18n/states-gb-legacy.php:84 +msgid "Newcastle upon Tyne" +msgstr "" + +#: i18n/states-gb-legacy.php:85 +msgid "Newham" +msgstr "" + +#: i18n/states-gb-legacy.php:86 +msgid "Newry, Mourne and Down" +msgstr "" + +#: i18n/states-gb-legacy.php:87 +msgid "North East Lincolnshire" +msgstr "" + +#: i18n/states-gb-legacy.php:88 +msgid "North Lincolnshire" +msgstr "" + +#: i18n/states-gb-legacy.php:89 +msgid "North Tyneside" +msgstr "" + +#: i18n/states-gb-legacy.php:90 +msgid "Nottingham" +msgstr "" + +#: i18n/states-gb-legacy.php:91 +msgid "Oldham" +msgstr "" + +#: i18n/states-gb-legacy.php:92 +msgid "Orkney Islands" +msgstr "" + +#: i18n/states-gb-legacy.php:93 +msgid "Peterborough" +msgstr "" + +#: i18n/states-gb-legacy.php:94 +msgid "Plymouth" +msgstr "" + +#: i18n/states-gb-legacy.php:95 +msgid "Poole" +msgstr "" + +#: i18n/states-gb-legacy.php:96 +msgid "Portsmouth" +msgstr "" + +#: i18n/states-gb-legacy.php:97 +msgid "Reading" +msgstr "" + +#: i18n/states-gb-legacy.php:98 +msgid "Redbridge" +msgstr "" + +#: i18n/states-gb-legacy.php:99 +msgid "Redcar and Cleveland" +msgstr "" + +#: i18n/states-gb-legacy.php:100 +msgid "Rhondda, Cynon, Taff" +msgstr "" + +#: i18n/states-gb-legacy.php:101 +msgid "Richmond upon Thames" +msgstr "" + +#: i18n/states-gb-legacy.php:102 +msgid "Rochdale" +msgstr "" + +#: i18n/states-gb-legacy.php:103 +msgid "Rotherham" +msgstr "" + +#: i18n/states-gb-legacy.php:104 +msgid "Salford" +msgstr "" + +#: i18n/states-gb-legacy.php:105 +msgid "Sandwell" +msgstr "" + +#: i18n/states-gb-legacy.php:106 +msgid "Scottish Borders, The" +msgstr "" + +#: i18n/states-gb-legacy.php:107 +msgid "Sefton" +msgstr "" + +#: i18n/states-gb-legacy.php:108 +msgid "Sheffield" +msgstr "" + +#: i18n/states-gb-legacy.php:109 +msgid "Slough" +msgstr "" + +#: i18n/states-gb-legacy.php:110 +msgid "Solihull" +msgstr "" + +#: i18n/states-gb-legacy.php:111 +msgid "South Tyneside" +msgstr "" + +#: i18n/states-gb-legacy.php:112 +msgid "Southampton" +msgstr "" + +#: i18n/states-gb-legacy.php:113 +msgid "Southend-on-Sea" +msgstr "" + +#: i18n/states-gb-legacy.php:114 +msgid "Southwark" +msgstr "" + +#: i18n/states-gb-legacy.php:115 +msgid "St. Helens" +msgstr "" + +#: i18n/states-gb-legacy.php:116 +msgid "Stirling" +msgstr "" + +#: i18n/states-gb-legacy.php:117 +msgid "Stockport" +msgstr "" + +#: i18n/states-gb-legacy.php:118 +msgid "Stockton-on-Tees" +msgstr "" + +#: i18n/states-gb-legacy.php:119 +msgid "Stoke-on-Trent" +msgstr "" + +#: i18n/states-gb-legacy.php:120 +msgid "Sunderland" +msgstr "" + +#: i18n/states-gb-legacy.php:121 +msgid "Sutton" +msgstr "" + +#: i18n/states-gb-legacy.php:122 +msgid "Swindon" +msgstr "" + +#: i18n/states-gb-legacy.php:123 +msgid "Tameside" +msgstr "" + +#: i18n/states-gb-legacy.php:124 +msgid "Telford and Wrekin" +msgstr "" + +#: i18n/states-gb-legacy.php:125 +msgid "Thurrock" +msgstr "" + +#: i18n/states-gb-legacy.php:126 +msgid "Torbay" +msgstr "" + +#: i18n/states-gb-legacy.php:127 +msgid "Tower Hamlets" +msgstr "" + +#: i18n/states-gb-legacy.php:128 +msgid "Trafford" +msgstr "" + +#: i18n/states-gb-legacy.php:129 +msgid "Vale of Glamorgan, The" +msgstr "" + +#: i18n/states-gb-legacy.php:130 +msgid "Wakefield" +msgstr "" + +#: i18n/states-gb-legacy.php:131 +msgid "Walsall" +msgstr "" + +#: i18n/states-gb-legacy.php:132 +msgid "Waltham Forest" +msgstr "" + +#: i18n/states-gb-legacy.php:133 +msgid "Wandsworth" +msgstr "" + +#: i18n/states-gb-legacy.php:134 +msgid "Warrington" +msgstr "" + +#: i18n/states-gb-legacy.php:135 +#: i18n/states-gb.php:105 +msgid "Warwickshire" +msgstr "" + +#: i18n/states-gb-legacy.php:136 +msgid "West Berkshire" +msgstr "" + +#: i18n/states-gb-legacy.php:137 +msgid "Westminster" +msgstr "" + +#: i18n/states-gb-legacy.php:138 +msgid "Wigan" +msgstr "" + +#: i18n/states-gb-legacy.php:139 +msgid "Windsor and Maidenhead" +msgstr "" + +#: i18n/states-gb-legacy.php:140 +msgid "Wirral" +msgstr "" + +#: i18n/states-gb-legacy.php:141 +msgid "Wokingham" +msgstr "" + +#: i18n/states-gb-legacy.php:142 +msgid "Wolverhampton" +msgstr "" + +#: i18n/states-gb-legacy.php:143 +msgid "York" +msgstr "" + +#: i18n/states-gb.php:7 +msgid "Aberdeenshire" +msgstr "" + +#: i18n/states-gb.php:8 +msgid "Angus" +msgstr "" + +#: i18n/states-gb.php:9 +msgid "Antrim" +msgstr "" + +#: i18n/states-gb.php:10 +msgid "Argyll" +msgstr "" + +#: i18n/states-gb.php:11 +msgid "Armagh" +msgstr "" + +#: i18n/states-gb.php:12 +msgid "Banffshire" +msgstr "" + +#: i18n/states-gb.php:13 +msgid "Bedfordshire" +msgstr "" + +#: i18n/states-gb.php:14 +msgid "Berkshire" +msgstr "" + +#: i18n/states-gb.php:15 +msgid "Berwickshire" +msgstr "" + +#: i18n/states-gb.php:16 +msgid "Blaenau Gwent" +msgstr "" + +#: i18n/states-gb.php:17 +msgid "Bridgend" +msgstr "" + +#: i18n/states-gb.php:18 +msgid "Bristol" +msgstr "" + +#: i18n/states-gb.php:19 +msgid "Buckinghamshire" +msgstr "" + +#: i18n/states-gb.php:20 +msgid "Caerphilly" +msgstr "" + +#: i18n/states-gb.php:21 +msgid "Caithness" +msgstr "" + +#: i18n/states-gb.php:22 +msgid "Cambridgeshire" +msgstr "" + +#: i18n/states-gb.php:23 +msgid "Cardiff" +msgstr "" + +#: i18n/states-gb.php:24 +msgid "Carmarthenshire" +msgstr "" + +#: i18n/states-gb.php:25 +msgid "Ceredigion" +msgstr "" + +#: i18n/states-gb.php:26 +msgid "Cheshire" +msgstr "" + +#: i18n/states-gb.php:27 +msgid "Clackmannanshire" +msgstr "" + +#: i18n/states-gb.php:28 +msgid "Conwy" +msgstr "" + +#: i18n/states-gb.php:29 +msgid "Cornwall" +msgstr "" + +#: i18n/states-gb.php:30 +msgid "County Down" +msgstr "" + +#: i18n/states-gb.php:31 +msgid "County Durham" +msgstr "" + +#: i18n/states-gb.php:32 +msgid "Cumbria" +msgstr "" + +#: i18n/states-gb.php:33 +msgid "Denbighshire" +msgstr "" + +#: i18n/states-gb.php:34 +msgid "Derbyshire" +msgstr "" + +#: i18n/states-gb.php:35 +msgid "Devon" +msgstr "" + +#: i18n/states-gb.php:36 +msgid "Dorset" +msgstr "" + +#: i18n/states-gb.php:37 +msgid "Dumfries and Galloway" +msgstr "" + +#: i18n/states-gb.php:38 +msgid "East Ayrshire" +msgstr "" + +#: i18n/states-gb.php:39 +msgid "East Dunbartonshire" +msgstr "" + +#: i18n/states-gb.php:40 +msgid "East Lothian" +msgstr "" + +#: i18n/states-gb.php:41 +msgid "East Renfrewshire" +msgstr "" + +#: i18n/states-gb.php:42 +msgid "East Riding of Yorkshire" +msgstr "" + +#: i18n/states-gb.php:43 +msgid "East Sussex" +msgstr "" + +#: i18n/states-gb.php:44 +msgid "Essex" +msgstr "" + +#: i18n/states-gb.php:45 +msgid "Fermanagh" +msgstr "" + +#: i18n/states-gb.php:46 +msgid "Fife" +msgstr "" + +#: i18n/states-gb.php:47 +msgid "Flintshire" +msgstr "" + +#: i18n/states-gb.php:48 +msgid "Gloucestershire" +msgstr "" + +#: i18n/states-gb.php:49 +msgid "Greater London" +msgstr "" + +#: i18n/states-gb.php:50 +msgid "Greater Manchester" +msgstr "" + +#: i18n/states-gb.php:51 +msgid "Gwynedd" +msgstr "" + +#: i18n/states-gb.php:52 +msgid "Hampshire" +msgstr "" + +#: i18n/states-gb.php:53 +msgid "Herefordshire" +msgstr "" + +#: i18n/states-gb.php:54 +msgid "Hertfordshire" +msgstr "" + +#: i18n/states-gb.php:55 +msgid "Highland" +msgstr "" + +#: i18n/states-gb.php:56 +msgid "Inverclyde" +msgstr "" + +#: i18n/states-gb.php:57 +msgid "Isle of Anglesey" +msgstr "" + +#: i18n/states-gb.php:58 +msgid "Isle of Wight" +msgstr "" + +#: i18n/states-gb.php:59 +msgid "Kent" +msgstr "" + +#: i18n/states-gb.php:60 +msgid "Kincardineshire" +msgstr "" + +#: i18n/states-gb.php:61 +msgid "Lancashire" +msgstr "" + +#: i18n/states-gb.php:62 +msgid "Leicestershire" +msgstr "" + +#: i18n/states-gb.php:63 +msgid "Lincolnshire" +msgstr "" + +#: i18n/states-gb.php:64 +msgid "Londonderry" +msgstr "" + +#: i18n/states-gb.php:65 +msgid "Merseyside" +msgstr "" + +#: i18n/states-gb.php:66 +msgid "Merthyr Tydfil" +msgstr "" + +#: i18n/states-gb.php:67 +msgid "Midlothian" +msgstr "" + +#: i18n/states-gb.php:68 +msgid "Monmouthshire" +msgstr "" + +#: i18n/states-gb.php:69 +msgid "Moray" +msgstr "" + +#: i18n/states-gb.php:70 +msgid "Neath Port Talbot" +msgstr "" + +#: i18n/states-gb.php:71 +msgid "Newport" +msgstr "" + +#: i18n/states-gb.php:72 +msgid "Norfolk" +msgstr "" + +#: i18n/states-gb.php:73 +msgid "North Ayrshire" +msgstr "" + +#: i18n/states-gb.php:74 +msgid "North Lanarkshire" +msgstr "" + +#: i18n/states-gb.php:75 +msgid "North Somerset" +msgstr "" + +#: i18n/states-gb.php:76 +msgid "North Yorkshire" +msgstr "" + +#: i18n/states-gb.php:77 +msgid "Northamptonshire" +msgstr "" + +#: i18n/states-gb.php:78 +msgid "Northumberland" +msgstr "" + +#: i18n/states-gb.php:79 +msgid "Nottinghamshire" +msgstr "" + +#: i18n/states-gb.php:80 +msgid "Orkney" +msgstr "" + +#: i18n/states-gb.php:81 +msgid "Oxfordshire" +msgstr "" + +#: i18n/states-gb.php:82 +msgid "Pembrokeshire" +msgstr "" + +#: i18n/states-gb.php:83 +msgid "Perth and Kinross" +msgstr "" + +#: i18n/states-gb.php:84 +msgid "Powys" +msgstr "" + +#: i18n/states-gb.php:85 +msgid "Renfrewshire" +msgstr "" + +#: i18n/states-gb.php:86 +msgid "Rhondda Cynon Taff" +msgstr "" + +#: i18n/states-gb.php:87 +msgid "Rutland" +msgstr "" + +#: i18n/states-gb.php:88 +msgid "Scottish Borders" +msgstr "" + +#: i18n/states-gb.php:89 +msgid "Shetland Islands" +msgstr "" + +#: i18n/states-gb.php:90 +msgid "Shropshire" +msgstr "" + +#: i18n/states-gb.php:91 +msgid "Somerset" +msgstr "" + +#: i18n/states-gb.php:92 +msgid "South Ayrshire" +msgstr "" + +#: i18n/states-gb.php:93 +msgid "South Gloucestershire" +msgstr "" + +#: i18n/states-gb.php:94 +msgid "South Lanarkshire" +msgstr "" + +#: i18n/states-gb.php:95 +msgid "South Yorkshire" +msgstr "" + +#: i18n/states-gb.php:96 +msgid "Staffordshire" +msgstr "" + +#: i18n/states-gb.php:97 +msgid "Stirlingshire" +msgstr "" + +#: i18n/states-gb.php:98 +msgid "Suffolk" +msgstr "" + +#: i18n/states-gb.php:99 +msgid "Surrey" +msgstr "" + +#: i18n/states-gb.php:100 +msgid "Swansea" +msgstr "" + +#: i18n/states-gb.php:101 +msgid "Torfaen" +msgstr "" + +#: i18n/states-gb.php:102 +msgid "Tyne & Wear" +msgstr "" + +#: i18n/states-gb.php:103 +msgid "Tyrone" +msgstr "" + +#: i18n/states-gb.php:104 +msgid "Vale of Glamorgan" +msgstr "" + +#: i18n/states-gb.php:106 +msgid "West Dunbartonshire" +msgstr "" + +#: i18n/states-gb.php:107 +msgid "West Lothian" +msgstr "" + +#: i18n/states-gb.php:108 +msgid "West Midlands" +msgstr "" + +#: i18n/states-gb.php:109 +msgid "West Sussex" +msgstr "" + +#: i18n/states-gb.php:110 +msgid "West Yorkshire" +msgstr "" + +#: i18n/states-gb.php:111 +msgid "Western Isles" +msgstr "" + +#: i18n/states-gb.php:112 +msgid "Wiltshire" +msgstr "" + +#: i18n/states-gb.php:113 +msgid "Worcestershire" +msgstr "" + +#: i18n/states-gb.php:114 +msgid "Wrexham" +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 "Dadra 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:99 +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:61 +msgid "Acre" +msgstr "" + +#: i18n/states.php:62 +msgid "Alagoas" +msgstr "" + +#: i18n/states.php:63 +msgid "Amapá" +msgstr "" + +#: i18n/states.php:64 +#: i18n/states.php:356 +msgid "Amazonas" +msgstr "" + +#: i18n/states.php:65 +msgid "Bahia" +msgstr "" + +#: i18n/states.php:66 +msgid "Ceará" +msgstr "" + +#: i18n/states.php:67 +#: i18n/states.php:279 +msgid "Distrito Federal" +msgstr "" + +#: i18n/states.php:68 +msgid "Espírito Santo" +msgstr "" + +#: i18n/states.php:69 +msgid "Goiás" +msgstr "" + +#: i18n/states.php:70 +msgid "Maranhão" +msgstr "" + +#: i18n/states.php:71 +msgid "Mato Grosso" +msgstr "" + +#: i18n/states.php:72 +msgid "Mato Grosso do Sul" +msgstr "" + +#: i18n/states.php:73 +msgid "Minas Gerais" +msgstr "" + +#: i18n/states.php:74 +msgid "Pará" +msgstr "" + +#: i18n/states.php:75 +msgid "Paraíba" +msgstr "" + +#: i18n/states.php:76 +msgid "Paraná" +msgstr "" + +#: i18n/states.php:77 +msgid "Pernambuco" +msgstr "" + +#: i18n/states.php:78 +msgid "Piauí" +msgstr "" + +#: i18n/states.php:79 +msgid "Rio de Janeiro" +msgstr "" + +#: i18n/states.php:80 +msgid "Rio Grande do Norte" +msgstr "" + +#: i18n/states.php:81 +msgid "Rio Grande do Sul" +msgstr "" + +#: i18n/states.php:82 +msgid "Rondônia" +msgstr "" + +#: i18n/states.php:83 +msgid "Roraima" +msgstr "" + +#: i18n/states.php:84 +msgid "Santa Catarina" +msgstr "" + +#: i18n/states.php:85 +msgid "São Paulo" +msgstr "" + +#: i18n/states.php:86 +msgid "Sergipe" +msgstr "" + +#: i18n/states.php:87 +msgid "Tocantins" +msgstr "" + +#: i18n/states.php:91 +msgid "Blagoevgrad" +msgstr "" + +#: i18n/states.php:92 +msgid "Burgas" +msgstr "" + +#: i18n/states.php:93 +msgid "Dobrich" +msgstr "" + +#: i18n/states.php:94 +msgid "Gabrovo" +msgstr "" + +#: i18n/states.php:95 +msgid "Haskovo" +msgstr "" + +#: i18n/states.php:96 +msgid "Kardzhali" +msgstr "" + +#: i18n/states.php:97 +msgid "Kyustendil" +msgstr "" + +#: i18n/states.php:98 +msgid "Lovech" +msgstr "" + +#: i18n/states.php:100 +msgid "Pazardzhik" +msgstr "" + +#: i18n/states.php:101 +msgid "Pernik" +msgstr "" + +#: i18n/states.php:102 +msgid "Pleven" +msgstr "" + +#: i18n/states.php:103 +msgid "Plovdiv" +msgstr "" + +#: i18n/states.php:104 +msgid "Razgrad" +msgstr "" + +#: i18n/states.php:105 +msgid "Ruse" +msgstr "" + +#: i18n/states.php:106 +msgid "Shumen" +msgstr "" + +#: i18n/states.php:107 +msgid "Silistra" +msgstr "" + +#: i18n/states.php:108 +msgid "Sliven" +msgstr "" + +#: i18n/states.php:109 +msgid "Smolyan" +msgstr "" + +#: i18n/states.php:110 +msgid "Sofia" +msgstr "" + +#: i18n/states.php:111 +msgid "Sofia-Grad" +msgstr "" + +#: i18n/states.php:112 +msgid "Stara Zagora" +msgstr "" + +#: i18n/states.php:113 +msgid "Targovishte" +msgstr "" + +#: i18n/states.php:114 +msgid "Varna" +msgstr "" + +#: i18n/states.php:115 +msgid "Veliko Tarnovo" +msgstr "" + +#: i18n/states.php:116 +msgid "Vidin" +msgstr "" + +#: i18n/states.php:117 +msgid "Vratsa" +msgstr "" + +#: i18n/states.php:118 +msgid "Yambol" +msgstr "" + +#: i18n/states.php:122 +msgid "Yunnan / 云南" +msgstr "" + +#: i18n/states.php:123 +msgid "Beijing / 北京" +msgstr "" + +#: i18n/states.php:124 +msgid "Tianjin / 天津" +msgstr "" + +#: i18n/states.php:125 +msgid "Hebei / 河北" +msgstr "" + +#: i18n/states.php:126 +msgid "Shanxi / 山西" +msgstr "" + +#: i18n/states.php:127 +msgid "Inner Mongolia / 內蒙古" +msgstr "" + +#: i18n/states.php:128 +msgid "Liaoning / 辽宁" +msgstr "" + +#: i18n/states.php:129 +msgid "Jilin / 吉林" +msgstr "" + +#: i18n/states.php:130 +msgid "Heilongjiang / 黑龙江" +msgstr "" + +#: i18n/states.php:131 +msgid "Shanghai / 上海" +msgstr "" + +#: i18n/states.php:132 +msgid "Jiangsu / 江苏" +msgstr "" + +#: i18n/states.php:133 +msgid "Zhejiang / 浙江" +msgstr "" + +#: i18n/states.php:134 +msgid "Anhui / 安徽" +msgstr "" + +#: i18n/states.php:135 +msgid "Fujian / 福建" +msgstr "" + +#: i18n/states.php:136 +msgid "Jiangxi / 江西" +msgstr "" + +#: i18n/states.php:137 +msgid "Shandong / 山东" +msgstr "" + +#: i18n/states.php:138 +msgid "Henan / 河南" +msgstr "" + +#: i18n/states.php:139 +msgid "Hubei / 湖北" +msgstr "" + +#: i18n/states.php:140 +msgid "Hunan / 湖南" +msgstr "" + +#: i18n/states.php:141 +msgid "Guangdong / 广东" +msgstr "" + +#: i18n/states.php:142 +msgid "Guangxi Zhuang / 广西壮族" +msgstr "" + +#: i18n/states.php:143 +msgid "Hainan / 海南" +msgstr "" + +#: i18n/states.php:144 +msgid "Chongqing / 重庆" +msgstr "" + +#: i18n/states.php:145 +msgid "Sichuan / 四川" +msgstr "" + +#: i18n/states.php:146 +msgid "Guizhou / 贵州" +msgstr "" + +#: i18n/states.php:147 +msgid "Shaanxi / 陕西" +msgstr "" + +#: i18n/states.php:148 +msgid "Gansu / 甘肃" +msgstr "" + +#: i18n/states.php:149 +msgid "Qinghai / 青海" +msgstr "" + +#: i18n/states.php:150 +msgid "Ningxia Hui / 宁夏" +msgstr "" + +#: i18n/states.php:151 +msgid "Macau / 澳门" +msgstr "" + +#: i18n/states.php:152 +msgid "Tibet / 西藏" +msgstr "" + +#: i18n/states.php:153 +msgid "Xinjiang / 新疆" +msgstr "" + +#: i18n/states.php:157 +msgid "Hong Kong Island" +msgstr "" + +#: i18n/states.php:158 +msgid "Kowloon" +msgstr "" + +#: i18n/states.php:159 +msgid "New Territories" +msgstr "" + +#: i18n/states.php:163 +msgid "Bács-Kiskun" +msgstr "" + +#: i18n/states.php:164 +msgid "Békés" +msgstr "" + +#: i18n/states.php:165 +msgid "Baranya" +msgstr "" + +#: i18n/states.php:166 +msgid "Borsod-Abaúj-Zemplén" +msgstr "" + +#: i18n/states.php:167 +msgid "Budapest" +msgstr "" + +#: i18n/states.php:168 +msgid "Csongrád" +msgstr "" + +#: i18n/states.php:169 +msgid "Fejér" +msgstr "" + +#: i18n/states.php:170 +msgid "Győr-Moson-Sopron" +msgstr "" + +#: i18n/states.php:171 +msgid "Hajdú-Bihar" +msgstr "" + +#: i18n/states.php:172 +msgid "Heves" +msgstr "" + +#: i18n/states.php:173 +msgid "Jász-Nagykun-Szolnok" +msgstr "" + +#: i18n/states.php:174 +msgid "Komárom-Esztergom" +msgstr "" + +#: i18n/states.php:175 +msgid "Nógrád" +msgstr "" + +#: i18n/states.php:176 +msgid "Pest" +msgstr "" + +#: i18n/states.php:177 +msgid "Somogy" +msgstr "" + +#: i18n/states.php:178 +msgid "Szabolcs-Szatmár-Bereg" +msgstr "" + +#: i18n/states.php:179 +msgid "Tolna" +msgstr "" + +#: i18n/states.php:180 +msgid "Vas" +msgstr "" + +#: i18n/states.php:181 +msgid "Veszprém" +msgstr "" + +#: i18n/states.php:182 +msgid "Zala" +msgstr "" + +#: i18n/states.php:187 +msgid "Daerah Istimewa Aceh" +msgstr "" + +#: i18n/states.php:188 +msgid "Sumatera Utara" +msgstr "" + +#: i18n/states.php:189 +msgid "Sumatera Barat" +msgstr "" + +#: i18n/states.php:190 +msgid "Riau" +msgstr "" + +#: i18n/states.php:191 +msgid "Kepulauan Riau" +msgstr "" + +#: i18n/states.php:192 +msgid "Jambi" +msgstr "" + +#: i18n/states.php:193 +msgid "Sumatera Selatan" +msgstr "" + +#: i18n/states.php:194 +msgid "Bangka Belitung" +msgstr "" + +#: i18n/states.php:195 +msgid "Bengkulu" +msgstr "" + +#: i18n/states.php:196 +msgid "Lampung" +msgstr "" + +#: i18n/states.php:197 +msgid "DKI Jakarta" +msgstr "" + +#: i18n/states.php:198 +msgid "Jawa Barat" +msgstr "" + +#: i18n/states.php:199 +msgid "Banten" +msgstr "" + +#: i18n/states.php:200 +msgid "Jawa Tengah" +msgstr "" + +#: i18n/states.php:201 +msgid "Jawa Timur" +msgstr "" + +#: i18n/states.php:202 +msgid "Daerah Istimewa Yogyakarta" +msgstr "" + +#: i18n/states.php:203 +msgid "Bali" +msgstr "" + +#: i18n/states.php:204 +msgid "Nusa Tenggara Barat" +msgstr "" + +#: i18n/states.php:205 +msgid "Nusa Tenggara Timur" +msgstr "" + +#: i18n/states.php:206 +msgid "Kalimantan Barat" +msgstr "" + +#: i18n/states.php:207 +msgid "Kalimantan Tengah" +msgstr "" + +#: i18n/states.php:208 +msgid "Kalimantan Timur" +msgstr "" + +#: i18n/states.php:209 +msgid "Kalimantan Selatan" +msgstr "" + +#: i18n/states.php:210 +msgid "Kalimantan Utara" +msgstr "" + +#: i18n/states.php:211 +msgid "Sulawesi Utara" +msgstr "" + +#: i18n/states.php:212 +msgid "Sulawesi Tengah" +msgstr "" + +#: i18n/states.php:213 +msgid "Sulawesi Tenggara" +msgstr "" + +#: i18n/states.php:214 +msgid "Sulawesi Barat" +msgstr "" + +#: i18n/states.php:215 +msgid "Sulawesi Selatan" +msgstr "" + +#: i18n/states.php:216 +msgid "Gorontalo" +msgstr "" + +#: i18n/states.php:217 +msgid "Maluku" +msgstr "" + +#: i18n/states.php:218 +msgid "Maluku Utara" +msgstr "" + +#: i18n/states.php:219 +msgid "Papua" +msgstr "" + +#: i18n/states.php:220 +msgid "Papua Barat" +msgstr "" + +#: i18n/states.php:224 +msgid "Khuzestan" +msgstr "" + +#: i18n/states.php:225 +msgid "Tehran" +msgstr "" + +#: i18n/states.php:226 +msgid "Ilaam" +msgstr "" + +#: i18n/states.php:227 +msgid "Bushehr" +msgstr "" + +#: i18n/states.php:228 +msgid "Ardabil" +msgstr "" + +#: i18n/states.php:229 +msgid "Isfahan" +msgstr "" + +#: i18n/states.php:230 +msgid "Yazd" +msgstr "" + +#: i18n/states.php:231 +msgid "Kermanshah" +msgstr "" + +#: i18n/states.php:232 +msgid "Kerman" +msgstr "" + +#: i18n/states.php:233 +msgid "Hamadan" +msgstr "" + +#: i18n/states.php:234 +msgid "Ghazvin" +msgstr "" + +#: i18n/states.php:235 +msgid "Zanjan" +msgstr "" + +#: i18n/states.php:236 +msgid "Luristan" +msgstr "" + +#: i18n/states.php:237 +msgid "Alborz" +msgstr "" + +#: i18n/states.php:238 +msgid "East Azerbaijan" +msgstr "" + +#: i18n/states.php:239 +msgid "West Azerbaijan" +msgstr "" + +#: i18n/states.php:240 +msgid "Chaharmahal and Bakhtiari" +msgstr "" + +#: i18n/states.php:241 +msgid "South Khorasan" +msgstr "" + +#: i18n/states.php:242 +msgid "Razavi Khorasan" +msgstr "" + +#: i18n/states.php:243 +msgid "North Khorasan" +msgstr "" + +#: i18n/states.php:244 +msgid "Semnan" +msgstr "" + +#: i18n/states.php:245 +msgid "Fars" +msgstr "" + +#: i18n/states.php:246 +msgid "Qom" +msgstr "" + +#: i18n/states.php:247 +msgid "Kurdistan" +msgstr "" + +#: i18n/states.php:248 +msgid "Kohgiluyeh and BoyerAhmad" +msgstr "" + +#: i18n/states.php:249 +msgid "Golestan" +msgstr "" + +#: i18n/states.php:250 +msgid "Gilan" +msgstr "" + +#: i18n/states.php:251 +msgid "Mazandaran" +msgstr "" + +#: i18n/states.php:252 +msgid "Markazi" +msgstr "" + +#: i18n/states.php:253 +msgid "Hormozgan" +msgstr "" + +#: i18n/states.php:254 +msgid "Sistan and Baluchestan" +msgstr "" + +#: i18n/states.php:260 +msgid "Johor" +msgstr "" + +#: i18n/states.php:261 +msgid "Kedah" +msgstr "" + +#: i18n/states.php:262 +msgid "Kelantan" +msgstr "" + +#: i18n/states.php:263 +msgid "Melaka" +msgstr "" + +#: i18n/states.php:264 +msgid "Negeri Sembilan" +msgstr "" + +#: i18n/states.php:265 +msgid "Pahang" +msgstr "" + +#: i18n/states.php:266 +msgid "Perak" +msgstr "" + +#: i18n/states.php:267 +msgid "Perlis" +msgstr "" + +#: i18n/states.php:268 +msgid "Pulau Pinang" +msgstr "" + +#: i18n/states.php:269 +msgid "Sabah" +msgstr "" + +#: i18n/states.php:270 +msgid "Sarawak" +msgstr "" + +#: i18n/states.php:271 +msgid "Selangor" +msgstr "" + +#: i18n/states.php:272 +msgid "Terengganu" +msgstr "" + +#: i18n/states.php:273 +msgid "W.P. Kuala Lumpur" +msgstr "" + +#: i18n/states.php:274 +msgid "W.P. Labuan" +msgstr "" + +#: i18n/states.php:275 +msgid "W.P. Putrajaya" +msgstr "" + +#: i18n/states.php:280 +msgid "Jalisco" +msgstr "" + +#: i18n/states.php:281 +msgid "Nuevo León" +msgstr "" + +#: i18n/states.php:282 +msgid "Aguascalientes" +msgstr "" + +#: i18n/states.php:283 +msgid "Baja California Norte" +msgstr "" + +#: i18n/states.php:284 +msgid "Baja California Sur" +msgstr "" + +#: i18n/states.php:285 +msgid "Campeche" +msgstr "" + +#: i18n/states.php:286 +msgid "Chiapas" +msgstr "" + +#: i18n/states.php:287 +msgid "Chihuahua" +msgstr "" + +#: i18n/states.php:288 +msgid "Coahuila" +msgstr "" + +#: i18n/states.php:289 +msgid "Colima" +msgstr "" + +#: i18n/states.php:290 +msgid "Durango" +msgstr "" + +#: i18n/states.php:291 +msgid "Guanajuato" +msgstr "" + +#: i18n/states.php:292 +msgid "Guerrero" +msgstr "" + +#: i18n/states.php:293 +msgid "Hidalgo" +msgstr "" + +#: i18n/states.php:294 +msgid "Edo. de México" +msgstr "" + +#: i18n/states.php:295 +msgid "Michoacán" +msgstr "" + +#: i18n/states.php:296 +msgid "Morelos" +msgstr "" + +#: i18n/states.php:297 +msgid "Nayarit" +msgstr "" + +#: i18n/states.php:298 +msgid "Oaxaca" +msgstr "" + +#: i18n/states.php:299 +msgid "Puebla" +msgstr "" + +#: i18n/states.php:300 +msgid "Querétaro" +msgstr "" + +#: i18n/states.php:301 +msgid "Quintana Roo" +msgstr "" + +#: i18n/states.php:302 +msgid "San Luis Potosí" +msgstr "" + +#: i18n/states.php:303 +msgid "Sinaloa" +msgstr "" + +#: i18n/states.php:304 +msgid "Sonora" +msgstr "" + +#: i18n/states.php:305 +msgid "Tabasco" +msgstr "" + +#: i18n/states.php:306 +msgid "Tamaulipas" +msgstr "" + +#: i18n/states.php:307 +msgid "Tlaxcala" +msgstr "" + +#: i18n/states.php:308 +msgid "Veracruz" +msgstr "" + +#: i18n/states.php:309 +msgid "Yucatán" +msgstr "" + +#: i18n/states.php:310 +msgid "Zacatecas" +msgstr "" + +#: i18n/states.php:315 +msgid "Drenthe" +msgstr "" + +#: i18n/states.php:316 +msgid "Flevoland" +msgstr "" + +#: i18n/states.php:317 +msgid "Friesland" +msgstr "" + +#: i18n/states.php:318 +msgid "Gelderland" +msgstr "" + +#: i18n/states.php:319 +msgid "Groningen" +msgstr "" + +#: i18n/states.php:320 +msgid "Limburg" +msgstr "" + +#: i18n/states.php:321 +msgid "North Brabant" +msgstr "" + +#: i18n/states.php:322 +msgid "North Holland" +msgstr "" + +#: i18n/states.php:323 +msgid "Overijssel" +msgstr "" + +#: i18n/states.php:324 +msgid "South Holland" +msgstr "" + +#: i18n/states.php:325 +msgid "Utrecht" +msgstr "" + +#: i18n/states.php:326 +msgid "Zeeland" +msgstr "" + +#: i18n/states.php:329 +msgid "Bonaire" +msgstr "" + +#: i18n/states.php:330 +msgid "Saba" +msgstr "" + +#: i18n/states.php:331 +msgid "Sint Eustatius" +msgstr "" + +#: i18n/states.php:335 +msgid "Auckland" +msgstr "" + +#: i18n/states.php:336 +msgid "Bay of Plenty" +msgstr "" + +#: i18n/states.php:337 +msgid "Canterbury" +msgstr "" + +#: i18n/states.php:338 +msgid "Hawke’s Bay" +msgstr "" + +#: i18n/states.php:339 +msgid "Manawatu-Wanganui" +msgstr "" + +#: i18n/states.php:340 +msgid "Marlborough" +msgstr "" + +#: i18n/states.php:341 +msgid "Nelson" +msgstr "" + +#: i18n/states.php:342 +msgid "Northland" +msgstr "" + +#: i18n/states.php:343 +msgid "Otago" +msgstr "" + +#: i18n/states.php:344 +msgid "Southland" +msgstr "" + +#: i18n/states.php:345 +msgid "Taranaki" +msgstr "" + +#: i18n/states.php:346 +msgid "Tasman" +msgstr "" + +#: i18n/states.php:347 +msgid "Waikato" +msgstr "" + +#: i18n/states.php:348 +msgid "Wairarapa" +msgstr "" + +#: i18n/states.php:349 +msgid "Wellington" +msgstr "" + +#: i18n/states.php:350 +msgid "West Coast" +msgstr "" + +#: i18n/states.php:354 +msgid "El Callao" +msgstr "" + +#: i18n/states.php:355 +msgid "Municipalidad Metropolitana de Lima" +msgstr "" + +#: i18n/states.php:357 +msgid "Ancash" +msgstr "" + +#: i18n/states.php:358 +msgid "Apurímac" +msgstr "" + +#: i18n/states.php:359 +msgid "Arequipa" +msgstr "" + +#: i18n/states.php:360 +msgid "Ayacucho" +msgstr "" + +#: i18n/states.php:361 +msgid "Cajamarca" +msgstr "" + +#: i18n/states.php:362 +msgid "Cusco" +msgstr "" + +#: i18n/states.php:363 +msgid "Huancavelica" +msgstr "" + +#: i18n/states.php:364 +msgid "Huánuco" +msgstr "" + +#: i18n/states.php:365 +msgid "Ica" +msgstr "" + +#: i18n/states.php:366 +msgid "Junín" +msgstr "" + +#: i18n/states.php:367 +msgid "La Libertad" +msgstr "" + +#: i18n/states.php:368 +msgid "Lambayeque" +msgstr "" + +#: i18n/states.php:369 +msgid "Lima" +msgstr "" + +#: i18n/states.php:370 +msgid "Loreto" +msgstr "" + +#: i18n/states.php:371 +msgid "Madre de Dios" +msgstr "" + +#: i18n/states.php:372 +msgid "Moquegua" +msgstr "" + +#: i18n/states.php:373 +msgid "Pasco" +msgstr "" + +#: i18n/states.php:374 +msgid "Piura" +msgstr "" + +#: i18n/states.php:375 +msgid "Puno" +msgstr "" + +#: i18n/states.php:376 +msgid "San Martín" +msgstr "" + +#: i18n/states.php:377 +msgid "Tacna" +msgstr "" + +#: i18n/states.php:378 +msgid "Tumbes" +msgstr "" + +#: i18n/states.php:379 +msgid "Ucayali" +msgstr "" + +#: i18n/states.php:383 +msgid "Eastern Cape" +msgstr "" + +#: i18n/states.php:384 +msgid "Free State" +msgstr "" + +#: i18n/states.php:385 +msgid "Gauteng" +msgstr "" + +#: i18n/states.php:386 +msgid "KwaZulu-Natal" +msgstr "" + +#: i18n/states.php:387 +msgid "Limpopo" +msgstr "" + +#: i18n/states.php:388 +msgid "Mpumalanga" +msgstr "" + +#: i18n/states.php:389 +msgid "Northern Cape" +msgstr "" + +#: i18n/states.php:390 +msgid "North West" +msgstr "" + +#: i18n/states.php:391 +msgid "Western Cape" +msgstr "" + +#: includes/admin/adjustments/adjustment-functions.php:53 +#: includes/admin/adjustments/adjustment-functions.php:73 +#: includes/admin/reporting/reports.php:2587 +#: includes/reports/reports-functions.php:348 +#: includes/upgrades/functions.php:148 +#: src/Admin/Menu/Header.php:154 +#: src/Admin/Menu/Pages.php:69 +#: src/Admin/Menu/Pages.php:70 +msgid "Discounts" +msgstr "" + +#: includes/admin/adjustments/adjustment-functions.php:54 +#: includes/admin/payments/payments-history.php:155 +#: includes/post-types.php:46 +msgid "Add New" +msgstr "" + +#: includes/admin/admin-bar.php:57 +msgid "Notifications" +msgstr "" + +#: includes/admin/admin-bar.php:70 +#: src/Admin/Menu/Pages.php:132 +msgid "Store Reports" +msgstr "" + +#: includes/admin/admin-bar.php:81 +msgid "Store Settings" +msgstr "" + +#. translators: %s: Downloads plural 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 +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:133 +#: includes/admin/admin-deprecated-functions.php:284 +#: includes/admin/plugins.php:39 +#: src/Admin/Discounts/Generate.php:82 +#: src/Admin/Promos/Notices/Lite.php:42 +#: src/Admin/Settings/Tabs/Gateways.php:315 +#: 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:148 +msgid "Development Domain %s" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:30 +#: src/Admin/Settings/Tabs/Gateways.php:135 +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:49 +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:54 +#: includes/admin/tools.php:245 +#: includes/admin/views/email-editor/header.php:51 +msgid "Save" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:79 +#: includes/admin/payments/actions.php:76 +#: includes/admin/payments/actions.php:81 +#: includes/admin/payments/actions.php:285 +#: includes/admin/payments/actions.php:317 +#: src/Emails/Triggers.php:63 +msgid "You do not have permission to edit this payment record" +msgstr "" + +#. translators: %s: refund link +#: includes/admin/admin-deprecated-functions.php:79 +#: includes/admin/admin-deprecated-functions.php:835 +#: includes/admin/customers/customer-actions.php:313 +#: includes/admin/customers/customer-actions.php:373 +#: includes/admin/customers/customer-actions.php:567 +#: includes/admin/customers/customer-actions.php:628 +#: includes/admin/discounts/discount-actions.php:32 +#: includes/admin/discounts/discount-actions.php:190 +#: includes/admin/discounts/discount-actions.php:195 +#: includes/admin/discounts/discount-actions.php:204 +#: includes/admin/discounts/discount-actions.php:345 +#: includes/admin/discounts/discount-actions.php:350 +#: includes/admin/discounts/discount-actions.php:355 +#: includes/admin/discounts/discount-actions.php:383 +#: includes/admin/discounts/discount-actions.php:388 +#: includes/admin/discounts/discount-actions.php:415 +#: includes/admin/discounts/discount-actions.php:446 +#: includes/admin/discounts/discount-codes.php:30 +#: includes/admin/discounts/discount-codes.php:39 +#: includes/admin/discounts/discount-codes.php:57 +#: 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:67 +#: includes/admin/import/class-batch-import-payments.php:77 +#: includes/admin/import/class-batch-import.php:206 +#: includes/admin/payments/actions.php:76 +#: includes/admin/payments/actions.php:81 +#: includes/admin/payments/actions.php:215 +#: includes/admin/payments/actions.php:253 +#: includes/admin/payments/actions.php:285 +#: includes/admin/payments/actions.php:317 +#: 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:43 +#: includes/admin/payments/view-order-details.php:62 +#: includes/admin/payments/view-refund.php:23 +#: includes/admin/payments/view-refund.php:32 +#: includes/admin/reporting/class-api-requests-logs-list-table.php:75 +#: includes/admin/reporting/class-export.php:196 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:89 +#: includes/admin/reporting/export/class-batch-export.php:178 +#: includes/admin/reporting/export/export-actions.php:24 +#: includes/admin/tools.php:1051 +#: includes/admin/tools.php:1057 +#: 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:125 +#: includes/admin/tools/class-edd-tools-recount-single-customer-stats.php:123 +#: includes/admin/tools/class-edd-tools-recount-store-earnings.php:157 +#: includes/admin/tools/class-edd-tools-reset-stats.php:128 +#: 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/api/class-edd-api.php:1979 +#: includes/api/class-edd-api.php:1983 +#: includes/api/class-edd-api.php:2005 +#: includes/api/class-edd-api.php:2015 +#: includes/blocks/includes/forms/recaptcha.php:196 +#: includes/cart/class-edd-cart.php:1438 +#: includes/cart/class-edd-cart.php:1452 +#: includes/class-edd-license-handler.php:479 +#: includes/deprecated-functions.php:489 +#: includes/deprecated-functions.php:506 +#: includes/deprecated-functions.php:514 +#: includes/deprecated-functions.php:916 +#: includes/download-functions.php:1521 +#: includes/EDD_SL_Plugin_Updater.php:553 +#: includes/emails/template.php:109 +#: includes/error-tracking.php:60 +#: includes/gateways/functions.php:339 +#: includes/gateways/manual.php:33 +#: includes/gateways/paypal-standard.php:172 +#: includes/gateways/paypal/admin/connect.php:629 +#: includes/gateways/paypal/admin/connect.php:633 +#: includes/gateways/paypal/admin/connect.php:669 +#: includes/gateways/paypal/admin/connect.php:673 +#: includes/gateways/paypal/admin/connect.php:829 +#: includes/gateways/paypal/admin/connect.php:841 +#: includes/gateways/paypal/checkout-actions.php:118 +#: includes/gateways/stripe/includes/admin/upgrade-functions.php:87 +#: includes/process-download.php:307 +#: includes/process-download.php:942 +#: includes/query-filters.php:51 +#: includes/user-functions.php:844 +#: includes/user-functions.php:868 +#: includes/user-functions.php:917 +#: includes/users/lost-password.php:252 +#: src/Admin/Upgrades/v3/Base.php:117 +#: src/Emails/Triggers.php:63 +#: src/Extensions/Updater.php:510 +msgid "Error" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:105 +msgid "Popular" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:106 +msgid "New" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:107 +#: includes/admin/class-list-table.php:138 +#: src/Gateways/Stripe/Admin/Settings.php:295 +msgid "All" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:179 +msgid "Apps and Integrations for Easy Digital Downloads" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:190 +#: includes/admin/admin-deprecated-functions.php:215 +msgid "Browse All Integrations" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:193 +msgid "These add functionality to your Easy Digital Downloads powered store." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:255 +msgid "These extensions could not be retrieved from the server. Please try again later." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:280 +#: src/Admin/Extensions/Menu.php:46 +msgid "EDD Extensions" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:280 +#: includes/admin/settings/contextual-help.php:140 +#: includes/admin/settings/register-settings.php:503 +#: src/Admin/Extensions/Menu.php:47 +msgid "Extensions" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:321 +msgid "System Information" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:324 +msgid "Use the system information below to help troubleshoot problems." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:335 +msgid "Download System Info File" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:336 +#: includes/admin/tools.php:1156 +msgid "Copy to Clipboard" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:835 +#: 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:87 +msgid "You do not have permission to do shop upgrades" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:947 +msgid "Preview Purchase Receipt" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:962 +#: src/Admin/Settings/Tabs/Emails.php:193 +msgid "Send Test Email" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1420 +#: src/Admin/Downloads/Metaboxes/Stats.php:88 +msgid "Net Sales:" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1425 +#: src/Admin/Downloads/Metaboxes/Stats.php:93 +msgid "Net Revenue:" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1448 +#: src/Admin/Downloads/Metaboxes/Stats.php:116 +msgid "View File Download Log" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1478 +#: src/Admin/Downloads/Editor/Details.php:77 +msgid "Product Type Options:" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1498 +#: src/Admin/Downloads/Editor/Details.php:83 +msgid "Sell this item as a single product with download files, or select a custom product type with different options, which may not necessarily include download files." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1540 +msgid "Click and drag to re-order price options" +msgstr "" + +#. translators: %s: price ID. +#: includes/admin/admin-deprecated-functions.php:1544 +msgid "Price ID: %s" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1555 +#: src/Admin/Assets/Localization.php:98 +msgid "Show advanced settings" +msgstr "" + +#. translators: %s: The bundle product index number. +#: includes/admin/admin-deprecated-functions.php:1562 +#: includes/admin/downloads/metabox.php:371 +#: includes/admin/downloads/views/metabox-bundled-products.php:137 +#: 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 +#: src/Admin/Assets/Localization.php:93 +#: src/Admin/Downloads/Editor/VariablePrices.php:347 +#: templates/checkout_cart.php:57 +#: templates/checkout_cart.php:75 +#: templates/shortcode-profile-editor.php:129 +msgid "Remove" +msgstr "" + +#. translators: %s: price ID. +#: includes/admin/admin-deprecated-functions.php:1565 +msgid "Remove price option %s" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1579 +#: includes/admin/admin-deprecated-functions.php:1588 +msgid "Option Name" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1598 +#: includes/admin/downloads/dashboard-columns.php:34 +#: includes/admin/downloads/dashboard-columns.php:330 +#: includes/admin/reporting/export/class-batch-export-downloads.php:51 +#: 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 +#: src/Admin/Downloads/Editor/Details.php:117 +#: src/Admin/Downloads/Editor/VariablePrices.php:178 +#: src/Emails/Tags/Registry.php:147 +#: templates/shortcode-receipt.php:211 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-order-by.js:10 +msgid "Price" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1630 +#: includes/gateways/stripe/includes/template-functions.php:470 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/buy-button-alignment.js:6 +msgid "Default" +msgstr "" + +#. translators: %s: price ID. +#: includes/admin/admin-deprecated-functions.php:1643 +msgid "Set ID %s as default price" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1711 +#: includes/admin/customers/customers.php:208 +msgctxt "Customer Details tab title" +msgid "Profile" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1715 +#: includes/admin/customers/customers.php:212 +msgctxt "Customer Emails tab title" +msgid "Emails" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1719 +#: includes/admin/customers/customers.php:216 +msgctxt "Customer Addresses tab title" +msgid "Addresses" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1723 +#: includes/admin/customers/customers.php:220 +msgctxt "Customer Notes tab title" +msgid "Notes" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1727 +#: includes/admin/customers/customers.php:224 +msgctxt "Customer Tools tab title" +msgid "Tools" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:1744 +#: includes/admin/customers/customers.php:233 +msgctxt "Delete Customer tab title" +msgid "Delete" +msgstr "" + +#: includes/admin/class-api-keys-table.php:126 +#: includes/user-functions.php:1074 +msgid "Never Used" +msgstr "" + +#. translators: %s: a length of time (e.g. "1 second") +#: includes/admin/class-api-keys-table.php:138 +#: includes/user-functions.php:1085 +#: src/Models/Notification.php:187 +msgid "%s ago" +msgstr "" + +#: includes/admin/class-api-keys-table.php:167 +msgid "View Log" +msgstr "" + +#: includes/admin/class-api-keys-table.php:185 +msgid "Reissue" +msgstr "" + +#: includes/admin/class-api-keys-table.php:202 +msgid "Revoke" +msgstr "" + +#: includes/admin/class-api-keys-table.php:218 +#: includes/blocks/views/checkout/purchase-form/register.php:7 +#: includes/checkout/template.php:508 +#: includes/checkout/template.php:517 +#: src/Emails/Tags/Registry.php:91 +#: templates/shortcode-register.php:20 +msgid "Username" +msgstr "" + +#: includes/admin/class-api-keys-table.php:219 +msgid "Public Key" +msgstr "" + +#: includes/admin/class-api-keys-table.php:220 +msgid "Token" +msgstr "" + +#: includes/admin/class-api-keys-table.php:221 +msgid "Secret Key" +msgstr "" + +#: includes/admin/class-api-keys-table.php:222 +msgid "Last Used" +msgstr "" + +#: includes/admin/class-api-keys-table.php:261 +msgid "Generate New API Keys" +msgstr "" + +#. translators: %s: URL to the settings page +#: includes/admin/class-edd-notices.php:286 +msgid "No checkout page is configured. Set one in Settings." +msgstr "" + +#. translators: %s: Uploads directory +#: includes/admin/class-edd-notices.php:358 +msgid "The files in %s are not currently protected." +msgstr "" + +#: includes/admin/class-edd-notices.php:359 +msgid "To protect them, you must add this NGINX redirect rule." +msgstr "" + +#. translators: %s: Dismiss notice URL +#: includes/admin/class-edd-notices.php:363 +msgid "If you have already done this, or it does not apply to your site, you may permanently dismiss this notice." +msgstr "" + +#. translators: %s: Uploads directory +#: includes/admin/class-edd-notices.php:396 +#: src/Admin/SiteHealth/Direct.php:217 +msgid "The .htaccess file is missing from: %s" +msgstr "" + +#. translators: %s: Uploads directory +#: includes/admin/class-edd-notices.php:398 +#: src/Admin/SiteHealth/Direct.php:222 +msgid "First, please re-save 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: %s: Notice Dismissal URL +#: includes/admin/class-edd-notices.php:400 +msgid "If you have already done this, or it does not apply to your site, you may permanently %s." +msgstr "" + +#. translators: %s: Notice Dismissal URL +#: includes/admin/class-edd-notices.php:400 +msgid "dismiss this notice" +msgstr "" + +#. translators: 1: link to the recount tool, 2: link to the plugins screen. +#: includes/admin/class-edd-notices.php:424 +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:468 +#: src/Admin/Notifications/GBLegacy.php:123 +msgid "Review Tax Rates" +msgstr "" + +#: includes/admin/class-edd-notices.php:475 +msgid "A default tax rate was detected." +msgstr "" + +#: includes/admin/class-edd-notices.php:475 +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:499 +msgid "Settings updated." +msgstr "" + +#. translators: %s: Next order number +#: includes/admin/class-edd-notices.php:512 +msgid "The sequential starting number could not be updated because it could conflict with existing orders. The next assigned order number is %s." +msgstr "" + +#. translators: %s: Sandbox doc link +#: includes/admin/class-edd-notices.php:528 +msgid "Connecting to PayPal in Test Mode requires the use of Sandbox Credentials. If you need help finding this, you can view our documentation on using PayPal Sandbox." +msgstr "" + +#: includes/admin/class-edd-notices.php:560 +msgid "Easy Digital Downloads is migrating orders. Sales and earnings data for your store will be updated when all orders have been migrated." +msgstr "" + +#. translators: 1: opening link tag, 2: opening link tag, 3: closing link tag. +#: includes/admin/class-edd-notices.php:583 +msgid "You are running an outdated version of the Easy Digital Downloads — Stripe Pro Payment Gateway. You may need to log into %1$syour account%3$s to download the latest version and %2$smanually upgrade%3$s it." +msgstr "" + +#. translators: 1: Opening anchor tag; %2$s: Closing anchor tag. +#: includes/admin/class-edd-notices.php:617 +msgid "New webhooks have been registered for PayPal Commerce, but we were unable to update them automatically. Please %1$ssync your webhooks manually%2$s." +msgstr "" + +#: includes/admin/class-edd-notices.php:650 +msgid "Discount code added." +msgstr "" + +#: includes/admin/class-edd-notices.php:658 +msgid "There was a problem adding that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:667 +msgid "A discount with that code already exists, please use a different code." +msgstr "" + +#: includes/admin/class-edd-notices.php:676 +msgid "Discount code updated." +msgstr "" + +#: includes/admin/class-edd-notices.php:684 +msgid "No changes were made to that discount code." +msgstr "" + +#: includes/admin/class-edd-notices.php:692 +msgid "There was a problem updating that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:701 +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:710 +msgid "The discount code entered is invalid; only alphanumeric characters are allowed, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:719 +msgid "The discount amount must be a valid percentage or numeric flat amount. Please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:728 +msgid "Discount code deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:736 +msgid "There was a problem deleting that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:745 +msgid "Discount code activated." +msgstr "" + +#: includes/admin/class-edd-notices.php:753 +msgid "There was a problem activating that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:762 +msgid "Discount code deactivated." +msgstr "" + +#: includes/admin/class-edd-notices.php:770 +msgid "There was a problem deactivating that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:779 +msgid "Discount code archived." +msgstr "" + +#: includes/admin/class-edd-notices.php:787 +msgid "There was a problem archiving that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:802 +msgid "The reports have been refreshed." +msgstr "" + +#: includes/admin/class-edd-notices.php:816 +msgid "The settings have been imported." +msgstr "" + +#: includes/admin/class-edd-notices.php:824 +msgid "API keys successfully generated." +msgstr "" + +#: includes/admin/class-edd-notices.php:832 +msgid "The specified user already has API keys." +msgstr "" + +#: includes/admin/class-edd-notices.php:841 +msgid "API keys successfully regenerated." +msgstr "" + +#: includes/admin/class-edd-notices.php:849 +msgid "API keys successfully revoked." +msgstr "" + +#: includes/admin/class-edd-notices.php:857 +msgid "The test email summary was sent successfully." +msgstr "" + +#: includes/admin/class-edd-notices.php:866 +msgid "Your extensions could not be refreshed because you have not verified your license key." +msgstr "" + +#: includes/admin/class-edd-notices.php:876 +msgid "Webhooks have been created for the Stripe gateway." +msgstr "" + +#: includes/admin/class-edd-notices.php:885 +msgid "There was an error creating webhooks for the Stripe gateway." +msgstr "" + +#: includes/admin/class-edd-notices.php:895 +msgid "Stripe webhooks could not be created because your site is not using HTTPS." +msgstr "" + +#: includes/admin/class-edd-notices.php:910 +msgid "The note has been added successfully." +msgstr "" + +#: includes/admin/class-edd-notices.php:918 +msgid "The order has been updated successfully." +msgstr "" + +#: includes/admin/class-edd-notices.php:926 +msgid "Order successfully created." +msgstr "" + +#: includes/admin/class-edd-notices.php:934 +msgid "The order has been moved to the trash." +msgstr "" + +#: includes/admin/class-edd-notices.php:942 +msgid "The order has been restored." +msgstr "" + +#: includes/admin/class-edd-notices.php:950 +msgid "The order has been deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:958 +msgid "The purchase receipt has been resent." +msgstr "" + +#: includes/admin/class-edd-notices.php:966 +msgid "The purchase receipt could not be resent." +msgstr "" + +#: includes/admin/class-edd-notices.php:975 +msgid "The purchase receipt may not have been sent." +msgstr "" + +#: includes/admin/class-edd-notices.php:984 +msgid "The order note has been deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:992 +msgid "The order values have been recalculated." +msgstr "" + +#: includes/admin/class-edd-notices.php:1000 +msgid "The order values could not be recalculated." +msgstr "" + +#: includes/admin/class-edd-notices.php:1015 +msgid "Customer successfully deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:1023 +msgid "User successfully verified." +msgstr "" + +#: includes/admin/class-edd-notices.php:1031 +msgid "Customer email added." +msgstr "" + +#: includes/admin/class-edd-notices.php:1039 +msgid "Customer email deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:1047 +msgid "Failed to delete customer email." +msgstr "" + +#: includes/admin/class-edd-notices.php:1056 +msgid "Primary email updated for customer." +msgstr "" + +#: includes/admin/class-edd-notices.php:1064 +msgid "Failed to set primary email." +msgstr "" + +#: includes/admin/class-edd-notices.php:1073 +msgid "Customer address deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:1081 +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:1095 +msgid "Congratulations! You are now running %1$sEasy Digital Downloads (Pro)%2$s." +msgstr "" + +#: includes/admin/class-edd-notices.php:1107 +msgid "The user capabilities for Easy Digital Downloads have been reset." +msgstr "" + +#: includes/admin/class-edd-notices.php:1149 +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:1152 +msgid "View Debug Log" +msgstr "" + +#: includes/admin/class-edd-notices.php:1153 +msgid "Delete Log File and Disable Logging" +msgstr "" + +#: includes/admin/class-edd-notices.php:1169 +#: includes/admin/customers/customer-actions.php:620 +#: 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:125 +#: includes/gateways/paypal/admin/connect.php:143 +#: includes/gateways/paypal/admin/connect.php:253 +#: includes/gateways/paypal/admin/connect.php:307 +#: includes/gateways/paypal/admin/connect.php:437 +#: includes/gateways/paypal/admin/connect.php:629 +#: includes/gateways/paypal/admin/connect.php:633 +#: includes/gateways/paypal/admin/connect.php:669 +#: includes/gateways/paypal/admin/connect.php:673 +#: includes/gateways/paypal/admin/connect.php:735 +#: includes/gateways/paypal/admin/connect.php:768 +#: includes/gateways/paypal/admin/connect.php:790 +#: includes/gateways/paypal/admin/connect.php:840 +#: src/Admin/Emails/Manager.php:50 +#: src/Admin/Emails/Manager.php:97 +#: src/Admin/Emails/Manager.php:203 +#: src/Admin/Promos/PromoHandler.php:158 +#: src/Admin/Promos/PromoHandler.php:165 +#: src/Admin/Promos/PromoHandler.php:170 +#: src/Admin/Promos/PromoHandler.php:194 +msgid "You do not have permission to perform this action." +msgstr "" + +#: includes/admin/class-edd-notices.php:1174 +msgid "The debug log has been cleared and logging has been disabled." +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:73 +#: includes/admin/customers/class-customer-email-addresses-table.php:80 +#: includes/admin/customers/customers.php:1022 +#: includes/admin/customers/customers.php:1208 +msgid "Primary" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:142 +#: includes/admin/customers/class-customer-email-addresses-table.php:135 +#: includes/admin/customers/class-customer-table.php:131 +#: includes/admin/payments/orders.php:1123 +#: includes/misc-functions.php:1546 +#: includes/payments/functions.php:537 +msgid "Pending" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:147 +#: includes/admin/customers/class-customer-email-addresses-table.php:140 +#: includes/admin/customers/class-customer-table.php:142 +#: includes/admin/customers/customers.php:613 +#: includes/admin/discounts/edit-discount.php:299 +#: includes/class-edd-discount.php:706 +#: includes/misc-functions.php:1541 +#: src/Admin/Promos/About.php:834 +msgid "Active" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:177 +#: includes/admin/customers/class-customer-email-addresses-table.php:170 +msgid "View" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:192 +#: includes/admin/customers/class-customer-addresses-table.php:333 +#: includes/admin/customers/class-customer-email-addresses-table.php:187 +#: includes/admin/customers/class-customer-email-addresses-table.php:321 +#: includes/admin/customers/class-customer-table.php:197 +#: includes/admin/customers/class-customer-table.php:285 +#: includes/admin/customers/customers.php:1058 +#: includes/admin/customers/customers.php:1211 +#: includes/admin/customers/customers.php:1290 +#: includes/admin/discounts/class-discount-codes-table.php:282 +#: includes/admin/discounts/class-discount-codes-table.php:451 +#: includes/gateways/stripe/includes/template-functions.php:507 +#: src/Admin/PassHandler/Handler.php:74 +#: src/Admin/Settings/Tabs/Privacy.php:130 +#: src/Licensing/Traits/Controls.php:83 +msgid "Delete" +msgstr "" + +#. translators: customer address id +#: includes/admin/customers/class-customer-addresses-table.php:260 +msgid "Address ID: %s" +msgstr "" + +#. translators: %s: the customer name +#: includes/admin/customers/class-customer-addresses-table.php:271 +msgctxt "Noun: The customer name" +msgid "Select %s" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:297 +#: includes/admin/customers/customers.php:1141 +#: includes/admin/customers/customers.php:1166 +#: includes/admin/payments/orders.php:136 +#: includes/admin/payments/orders.php:165 +#: 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:63 +#: includes/blocks/views/checkout/purchase-form/address.php:32 +msgid "Address" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:298 +#: includes/admin/customers/customers.php:1143 +#: includes/admin/customers/customers.php:1184 +#: includes/admin/views/tmpl-tax-rates-table-add.php:36 +#: 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:299 +#: includes/admin/customers/customers.php:1145 +#: includes/admin/customers/customers.php:1198 +#: 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:67 +#: includes/admin/tools.php:690 +#: includes/admin/views/tmpl-tax-rates-table-add.php:18 +#: 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:584 +#: includes/gateways/stripe/includes/template-functions.php:594 +#: templates/shortcode-profile-editor.php:168 +msgid "Country" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:300 +#: includes/admin/customers/class-customer-email-addresses-table.php:290 +#: includes/admin/customers/customer-functions.php:79 +#: includes/admin/payments/class-payments-table.php:425 +#: includes/admin/payments/orders.php:124 +#: includes/admin/payments/orders.php:159 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:127 +#: includes/admin/reporting/class-sales-logs-list-table.php:98 +#: src/Emails/Templates/Registry.php:78 +msgid "Customer" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:301 +#: includes/admin/customers/class-customer-email-addresses-table.php:291 +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:17 +msgid "Type" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:302 +#: includes/admin/customers/class-customer-email-addresses-table.php:292 +#: includes/admin/customers/class-customer-table.php:256 +#: includes/admin/customers/customers.php:843 +#: includes/admin/customers/customers.php:893 +#: includes/admin/downloads/dashboard-columns.php:37 +#: includes/admin/payments/class-payments-table.php:428 +#: includes/admin/payments/orders.php:1246 +#: includes/admin/reporting/class-api-requests-logs-list-table.php:43 +#: includes/admin/reporting/class-export-download-history.php:59 +#: includes/admin/reporting/class-export-payments.php:76 +#: includes/admin/reporting/class-export.php:84 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:132 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:92 +#: 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:50 +#: 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:66 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:78 +#: includes/admin/tools.php:555 +#: includes/blocks/views/orders/totals.php:39 +#: includes/privacy-functions.php:991 +#: includes/reports/reports-functions.php:328 +#: templates/history-purchases.php:52 +#: templates/shortcode-receipt.php:69 +msgid "Date" +msgstr "" + +#: includes/admin/customers/class-customer-email-addresses-table.php:81 +msgid "Secondary" +msgstr "" + +#: includes/admin/customers/class-customer-email-addresses-table.php:254 +msgid "Primary email addresses cannot be deleted." +msgstr "" + +#. translators: %s: the customer email address +#: includes/admin/customers/class-customer-email-addresses-table.php:261 +msgctxt "Noun: The customer email address" +msgid "Select %s" +msgstr "" + +#: includes/admin/customers/class-customer-email-addresses-table.php:289 +#: includes/admin/customers/class-customer-table.php:253 +#: includes/admin/customers/customers.php:1006 +#: includes/admin/payments/orders.php:130 +#: includes/admin/payments/orders.php:285 +#: 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:57 +#: includes/admin/reporting/export/class-batch-export-taxed-customers.php:43 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:59 +#: includes/admin/tools.php:446 +#: includes/blocks/views/forms/registration.php:25 +#: src/Admin/Emails/ListTable.php:75 +#: src/Emails/Tags/Registry.php:98 +#: templates/shortcode-register.php:25 +#: includes/blocks/build/register/index.js:1 +#: includes/blocks/src/register/edit.js:51 +msgid "Email" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:134 +#: includes/admin/customers/customers.php:615 +#: includes/admin/views/email-editor/status.php:25 +#: includes/class-edd-cli.php:69 +#: includes/class-edd-cli.php:79 +#: includes/class-edd-cli.php:88 +#: includes/class-edd-cli.php:99 +#: includes/class-edd-cli.php:160 +#: includes/gateways/paypal-standard.php:94 +#: src/Admin/Settings/Tabs/Gateways.php:121 +msgid "Disabled" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:137 +#: includes/admin/customers/customers.php:614 +#: includes/admin/discounts/edit-discount.php:300 +#: includes/class-edd-discount.php:700 +#: includes/misc-functions.php:1542 +#: src/Admin/Promos/About.php:840 +msgid "Inactive" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:195 +#: includes/admin/discounts/class-discount-codes-table.php:220 +#: includes/admin/payments/class-payments-table.php:568 +#: src/Admin/Downloads/Editor/VariablePrices.php:240 +#: src/Emails/Templates/Traits/Actions.php:41 +msgid "Edit" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:196 +#: includes/admin/payments/orders.php:596 +#: includes/upgrades/functions.php:168 +#: src/Admin/Emails/Screen.php:37 +#: src/Admin/Tools/Screen.php:72 +msgid "Logs" +msgstr "" + +#. translators: %s: the customer name or email address +#: includes/admin/customers/class-customer-table.php:227 +msgctxt "Noun: The customer name or Email Address" +msgid "Select %s" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:252 +#: includes/admin/discounts/add-discount.php:31 +#: includes/admin/discounts/class-discount-codes-table.php:79 +#: 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:58 +#: includes/admin/reporting/export/class-batch-export-taxed-customers.php:42 +#: includes/admin/tools.php:455 +#: includes/privacy-functions.php:639 +#: includes/reports/data/discounts/class-top-five-discounts-list-table.php:75 +#: 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 +#: src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php:178 +#: templates/shortcode-receipt.php:204 +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:35 +msgid "Name" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:254 +#: includes/admin/payments/payments-history.php:148 +#: includes/orders/functions/types.php:91 +#: includes/upgrades/functions.php:152 +#: src/Admin/Menu/Header.php:151 +#: src/Admin/Menu/Pages.php:57 +#: src/Admin/Menu/Pages.php:58 +msgid "Orders" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:255 +msgid "Spent" +msgstr "" + +#: includes/admin/customers/customer-actions.php:33 +#: includes/admin/customers/customer-actions.php:196 +#: includes/admin/customers/customer-actions.php:317 +#: includes/admin/customers/customer-actions.php:377 +#: includes/admin/customers/customer-actions.php:506 +#: includes/admin/customers/customer-actions.php:571 +msgid "You do not have permission to edit this customer." +msgstr "" + +#: includes/admin/customers/customer-actions.php:42 +#: includes/admin/customers/customer-actions.php:216 +#: includes/admin/customers/customer-actions.php:313 +#: includes/admin/customers/customer-actions.php:373 +#: includes/admin/customers/customer-actions.php:434 +#: includes/admin/customers/customer-actions.php:517 +#: includes/admin/customers/customer-actions.php:567 +#: includes/admin/customers/customer-actions.php:628 +#: includes/admin/discounts/discount-actions.php:345 +#: includes/admin/discounts/discount-actions.php:383 +#: includes/admin/discounts/discount-actions.php:415 +#: includes/admin/discounts/discount-actions.php:446 +#: includes/admin/import/import-functions.php:27 +#: includes/admin/import/import-functions.php:108 +#: includes/admin/reporting/export/export-actions.php:24 +#: includes/api/class-edd-api.php:1979 +#: includes/class-edd-license-handler.php:479 +#: includes/emails/recapture.php:24 +#: includes/user-functions.php:868 +#: src/Admin/Emails/Manager.php:46 +#: src/Admin/Emails/Manager.php:207 +msgid "Nonce verification failed." +msgstr "" + +#: includes/admin/customers/customer-actions.php:60 +#: src/Checkout/Errors.php:101 +#: src/Checkout/Errors.php:128 +msgid "Please enter a valid email address." +msgstr "" + +#. translators: %d: user ID +#: includes/admin/customers/customer-actions.php:68 +msgid "The User ID %d is already associated with a different customer." +msgstr "" + +#. translators: %d: user ID +#: includes/admin/customers/customer-actions.php:75 +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:102 +msgid "Failed to attach user. The login or email address %s was not found." +msgstr "" + +#: includes/admin/customers/customer-actions.php:206 +msgid "Email address is missing." +msgstr "" + +#: includes/admin/customers/customer-actions.php:208 +msgid "Customer ID is required." +msgstr "" + +#: includes/admin/customers/customer-actions.php:210 +msgid "An error has occured. Please try again." +msgstr "" + +#: includes/admin/customers/customer-actions.php:222 +msgid "Invalid email address." +msgstr "" + +#: includes/admin/customers/customer-actions.php:237 +msgid "Email already associated with this customer." +msgstr "" + +#: includes/admin/customers/customer-actions.php:243 +msgid "Email address is already associated with another customer." +msgstr "" + +#: includes/admin/customers/customer-actions.php:259 +msgid "Email successfully added to customer." +msgstr "" + +#. translators: 1: email address, 2: username +#: includes/admin/customers/customer-actions.php:266 +msgid "Email address %1$s added by %2$s" +msgstr "" + +#. translators: 1: email address, 2: username +#: includes/admin/customers/customer-actions.php:271 +#: includes/admin/customers/customer-actions.php:393 +msgid "Email address %1$s set as primary by %2$s" +msgstr "" + +#. translators: 1: email address, 2: username +#: includes/admin/customers/customer-actions.php:333 +#: includes/shortcodes.php:997 +msgid "Email address %1$s removed by %2$s" +msgstr "" + +#: includes/admin/customers/customer-actions.php:421 +msgid "You do not have permission to delete this customer." +msgstr "" + +#: includes/admin/customers/customer-actions.php:438 +msgid "Please confirm you want to delete this customer" +msgstr "" + +#: includes/admin/customers/customer-actions.php:484 +msgid "Error deleting customer" +msgstr "" + +#: includes/admin/customers/customer-actions.php:489 +msgid "Invalid Customer ID" +msgstr "" + +#: includes/admin/customers/customer-actions.php:536 +msgid "Failed to disconnect user from customer" +msgstr "" + +#. translators: link to send an email +#: includes/admin/customers/customer-functions.php:55 +#: 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:56 +msgid "Account Pending Verification" +msgstr "" + +#: includes/admin/customers/customer-functions.php:101 +#: includes/admin/reporting/class-sales-logs-list-table.php:64 +msgid "Unnamed Customer" +msgstr "" + +#: includes/admin/customers/customers.php:62 +#: includes/admin/reporting/reports.php:2908 +#: src/Admin/Menu/Header.php:157 +#: src/Admin/Menu/Pages.php:63 +#: src/Admin/Menu/Pages.php:64 +msgid "Customers" +msgstr "" + +#: includes/admin/customers/customers.php:63 +msgid "Email Addresses" +msgstr "" + +#: includes/admin/customers/customers.php:64 +msgid "Physical Addresses" +msgstr "" + +#. translators: the active screen, eg "Search Customers" or "Search Customer Email Addresses" +#: includes/admin/customers/customers.php:298 +msgctxt "Noun: Customers or Customer Email Addresses placeholder for a search box" +msgid "Search %s" +msgstr "" + +#: includes/admin/customers/customers.php:329 +msgid "You are not permitted to view this data." +msgstr "" + +#: includes/admin/customers/customers.php:336 +#: includes/admin/customers/customers.php:343 +msgid "Invalid Customer ID Provided." +msgstr "" + +#: includes/admin/customers/customers.php:350 +msgid "Customer Details" +msgstr "" + +#: includes/admin/customers/customers.php:521 +msgid "Edit Profile" +msgstr "" + +#: includes/admin/customers/customers.php:527 +#: includes/admin/thickbox.php:183 +#: includes/gateways/stripe/includes/template-functions.php:682 +#: includes/gateways/stripe/includes/template-functions.php:725 +#: src/Admin/Assets/Localization.php:108 +#: src/Admin/Emails/Reset.php:52 +msgid "Cancel" +msgstr "" + +#: includes/admin/customers/customers.php:528 +#: includes/gateways/stripe/includes/template-functions.php:491 +msgid "Update" +msgstr "" + +#: includes/admin/customers/customers.php:538 +msgid "Customer Address" +msgstr "" + +#: includes/admin/customers/customers.php:550 +msgid "Address 1" +msgstr "" + +#: includes/admin/customers/customers.php:551 +msgid "Address 2" +msgstr "" + +#: includes/admin/customers/customers.php:552 +#: includes/admin/customers/customers.php:1142 +#: includes/admin/customers/customers.php:1177 +#: 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:65 +#: includes/admin/tools.php:662 +#: 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:556 +#: templates/shortcode-profile-editor.php:158 +msgid "City" +msgstr "" + +#: includes/admin/customers/customers.php:576 +#: includes/admin/tools.php:671 +#: templates/shortcode-profile-editor.php:177 +msgid "State / Province" +msgstr "" + +#: includes/admin/customers/customers.php:580 +#: includes/admin/customers/customers.php:1144 +#: includes/admin/customers/customers.php:1191 +msgid "Postal Code" +msgstr "" + +#: includes/admin/customers/customers.php:587 +#: includes/admin/reporting/export/class-batch-export-payments.php:45 +msgid "Customer Name" +msgstr "" + +#: includes/admin/customers/customers.php:594 +#: includes/admin/payments/orders.php:368 +msgid "Customer Email" +msgstr "" + +#: includes/admin/customers/customers.php:600 +msgid "Customer Since" +msgstr "" + +#. translators: %s: i18n formatted date that the customer was created +#: includes/admin/customers/customers.php:606 +#: includes/admin/payments/orders.php:243 +msgid "Customer since %s" +msgstr "" + +#. translators: %s: user id +#: includes/admin/customers/customers.php:629 +msgid "User %s missing" +msgstr "" + +#: includes/admin/customers/customers.php:638 +msgid "Not a registered user" +msgstr "" + +#. translators: the customer's lifetime number of sales +#: includes/admin/customers/customers.php:678 +msgid "%s Completed Sale" +msgid_plural "%s Completed Sales" +msgstr[0] "" +msgstr[1] "" + +#. translators: the customer's lifetime value +#: includes/admin/customers/customers.php:698 +msgid "%s Lifetime Value" +msgstr "" + +#: includes/admin/customers/customers.php:717 +msgid "Agreements" +msgstr "" + +#: includes/admin/customers/customers.php:729 +#: includes/admin/customers/customers.php:759 +msgid " — Agreed to Terms" +msgstr "" + +#: includes/admin/customers/customers.php:745 +#: includes/admin/customers/customers.php:800 +msgid "Previous Agreement Dates" +msgstr "" + +#: includes/admin/customers/customers.php:762 +msgid "Estimated Terms Agreement Date" +msgstr "" + +#: includes/admin/customers/customers.php:763 +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:768 +msgid "No terms agreement found." +msgstr "" + +#: includes/admin/customers/customers.php:784 +#: includes/admin/customers/customers.php:815 +msgid " — Agreed to Privacy Policy" +msgstr "" + +#: includes/admin/customers/customers.php:818 +msgid "Estimated Privacy Policy Date" +msgstr "" + +#: includes/admin/customers/customers.php:819 +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:824 +msgid "No privacy policy agreement found." +msgstr "" + +#: includes/admin/customers/customers.php:836 +#: includes/admin/dashboard-widgets.php:224 +msgid "Recent Orders" +msgstr "" + +#: includes/admin/customers/customers.php:840 +#: includes/admin/customers/customers.php:890 +#: includes/admin/payments/class-payments-table.php:424 +msgid "Number" +msgstr "" + +#: includes/admin/customers/customers.php:841 +#: includes/admin/customers/customers.php:891 +#: includes/admin/payments/class-payments-table.php:426 +#: includes/admin/payments/orders.php:943 +#: includes/admin/payments/orders.php:949 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:91 +#: includes/admin/reporting/class-gateways-reports-table.php:74 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:75 +#: includes/reports/data/payment-gateways/class-gateway-stats-list-table.php:78 +msgid "Gateway" +msgstr "" + +#: includes/admin/customers/customers.php:842 +#: includes/admin/customers/customers.php:892 +#: includes/admin/payments/class-payments-table.php:427 +#: includes/admin/payments/class-refund-items-table.php:90 +#: includes/admin/tools.php:612 +#: 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:64 +#: includes/gateways/stripe/includes/payment-methods/payment-request/functions.php:149 +#: includes/gateways/stripe/includes/payment-methods/payment-request/functions.php:196 +#: templates/checkout_cart.php:129 +#: templates/shortcode-receipt.php:163 +msgid "Total" +msgstr "" + +#: includes/admin/customers/customers.php:881 +msgid "No orders found" +msgstr "" + +#: includes/admin/customers/customers.php:886 +msgid "Recent Refunds" +msgstr "" + +#: includes/admin/customers/customers.php:920 +msgid "No refunds found" +msgstr "" + +#. translators: %s: plural downloads label +#: includes/admin/customers/customers.php:928 +msgid "Purchased %s" +msgstr "" + +#. translators: %s: plural downloads label +#: includes/admin/customers/customers.php:953 +#: includes/blocks/build/buy-button/index.js:7 +#: includes/blocks/build/downloads/index.js:3 +#: includes/blocks/src/utilities/download-new.js:14 +msgid "No %s Found" +msgstr "" + +#: includes/admin/customers/customers.php:991 +msgid "Customer Emails" +msgstr "" + +#: includes/admin/customers/customers.php:994 +msgid "This customer can use any of the emails listed here when making new purchases." +msgstr "" + +#: includes/admin/customers/customers.php:1007 +msgid "Date Added" +msgstr "" + +#: includes/admin/customers/customers.php:1045 +#: includes/admin/customers/customers.php:1100 +msgid "Make Primary" +msgstr "" + +#: includes/admin/customers/customers.php:1083 +msgid "No emails found." +msgstr "" + +#: includes/admin/customers/customers.php:1092 +#: includes/admin/customers/customers.php:1094 +#: includes/admin/payments/orders.php:341 +#: includes/blocks/views/orders/guest.php:10 +#: includes/gateways/stripe/includes/payment-methods/buy-now/template.php:104 +#: includes/privacy-functions.php:818 +msgid "Email Address" +msgstr "" + +#: includes/admin/customers/customers.php:1105 +msgid "Add Email" +msgstr "" + +#: includes/admin/customers/customers.php:1134 +#: includes/upgrades/functions.php:156 +msgid "Customer Addresses" +msgstr "" + +#: includes/admin/customers/customers.php:1146 +#: includes/admin/customers/customers.php:1205 +msgid "First Used" +msgstr "" + +#: includes/admin/customers/customers.php:1246 +#: includes/admin/payments/orders.php:142 +#: includes/admin/reporting/export/class-batch-export-downloads.php:56 +#: src/Admin/Downloads/Editor/Notes.php:54 +msgid "Notes" +msgstr "" + +#: includes/admin/customers/customers.php:1296 +msgid "Are you sure you want to delete this customer?" +msgstr "" + +#: includes/admin/customers/customers.php:1308 +msgid "Delete all associated payments and records?" +msgstr "" + +#: includes/admin/customers/customers.php:1318 +msgid "Delete Customer" +msgstr "" + +#: includes/admin/customers/customers.php:1343 +#: includes/admin/payments/orders.php:148 +#: src/Admin/Menu/Header.php:160 +#: src/Admin/Menu/Pages.php:94 +#: src/Admin/Onboarding/Wizard.php:248 +msgid "Tools" +msgstr "" + +#: includes/admin/customers/customers.php:1346 +#: includes/admin/tools.php:59 +msgid "Recount Customer Stats" +msgstr "" + +#: includes/admin/customers/customers.php:1347 +msgid "Use this tool to recalculate the purchase count and total value of the customer." +msgstr "" + +#: includes/admin/customers/customers.php:1354 +#: includes/admin/tools.php:32 +msgid "Recount Stats" +msgstr "" + +#: includes/admin/customers/customers.php:1390 +msgid "This customer's user account is pending verification." +msgstr "" + +#: includes/admin/customers/customers.php:1392 +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:153 +#: includes/admin/dashboard-widgets.php:175 +#: includes/admin/dashboard-widgets.php:195 +#: includes/admin/reporting/reports.php:256 +#: includes/admin/reporting/reports.php:284 +#: includes/admin/reporting/reports.php:372 +#: includes/admin/reporting/reports.php:400 +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:78 +msgid "Net" +msgstr "" + +#: includes/admin/dashboard-widgets.php:139 +#: includes/admin/dashboard-widgets.php:159 +#: includes/admin/dashboard-widgets.php:180 +#: 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:266 +#: includes/admin/reporting/reports.php:372 +#: includes/admin/reporting/reports.php:380 +#: includes/admin/reporting/reports.php:762 +#: includes/admin/reporting/reports.php:783 +#: includes/admin/reporting/reports.php:908 +#: includes/admin/reporting/reports.php:1532 +#: includes/admin/reporting/reports.php:1775 +#: includes/admin/reporting/reports.php:1796 +#: includes/admin/reporting/reports.php:1892 +#: includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php:61 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-order-by.js:26 +msgid "Earnings" +msgstr "" + +#: includes/admin/dashboard-widgets.php:142 +#: includes/admin/dashboard-widgets.php:165 +#: includes/admin/dashboard-widgets.php:184 +#: includes/admin/dashboard-widgets.php:205 +msgid "Sale" +msgid_plural "Sales" +msgstr[0] "" +msgstr[1] "" + +#: includes/admin/dashboard-widgets.php:153 +#: includes/class-edd-stats.php:79 +#: includes/reports/reports-functions.php:536 +msgid "Today" +msgstr "" + +#: includes/admin/dashboard-widgets.php:175 +#: includes/class-edd-stats.php:84 +#: includes/reports/reports-functions.php:542 +msgid "Last Month" +msgstr "" + +#: includes/admin/dashboard-widgets.php:195 +msgid "All Time" +msgstr "" + +#: includes/admin/dashboard-widgets.php:201 +#: includes/admin/reporting/class-categories-reports-table.php:78 +#: includes/admin/tools.php:909 +#: src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php:180 +msgid "Total Earnings" +msgstr "" + +#: includes/admin/dashboard-widgets.php:241 +msgid "No Name" +msgstr "" + +#. translators: 1: customer name, 2: number of items purchased, 3: order total +#: includes/admin/dashboard-widgets.php:246 +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:270 +msgid "View All Orders" +msgstr "" + +#. translators: %s: Download label singular +#: includes/admin/dashboard-widgets.php:304 +msgid "1 %s" +msgstr "" + +#. translators: 1: Number of downloads, 2: Download label plural +#: includes/admin/dashboard-widgets.php:310 +msgid "%1$d %2$s" +msgstr "" + +#: includes/admin/discounts/add-discount.php:16 +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:81 +#: 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:47 +#: includes/admin/discounts/edit-discount.php:87 +msgid "10PERCENT" +msgstr "" + +#: includes/admin/discounts/add-discount.php:51 +#: 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:61 +#: includes/admin/discounts/class-discount-codes-table.php:82 +#: includes/admin/discounts/edit-discount.php:98 +#: includes/admin/payments/orders.php:856 +#: includes/admin/payments/refunds.php:232 +#: 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:72 +#: includes/admin/views/tmpl-order-adjustment-discount.php:41 +#: includes/admin/views/tmpl-order-adjustment.php:59 +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:80 +#: includes/admin/views/tmpl-order-form-add-order-item.php:226 +#: 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:65 +#: includes/admin/discounts/edit-discount.php:102 +msgid "10.00" +msgstr "" + +#: includes/admin/discounts/add-discount.php:66 +#: includes/admin/discounts/edit-discount.php:103 +msgid "Amount Type" +msgstr "" + +#: includes/admin/discounts/add-discount.php:72 +#: includes/admin/discounts/edit-discount.php:109 +msgid "The amount as a percentage or flat rate. Cannot be left blank." +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/discounts/add-discount.php:81 +#: includes/admin/discounts/edit-discount.php:117 +msgid "%s Requirements" +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/discounts/add-discount.php:93 +#: includes/admin/discounts/add-discount.php:141 +#: includes/admin/discounts/edit-discount.php:129 +#: includes/admin/discounts/edit-discount.php:177 +msgctxt "Noun: The plural label for the download post type as a placeholder for a dropdown" +msgid "Select %s" +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/discounts/add-discount.php:102 +#: includes/admin/discounts/edit-discount.php:137 +msgid "Cart must contain all selected %s" +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/discounts/add-discount.php:104 +#: includes/admin/discounts/edit-discount.php:138 +msgid "Cart needs one or more of the selected %s" +msgstr "" + +#: includes/admin/discounts/add-discount.php:110 +#: includes/admin/discounts/edit-discount.php:144 +msgid "Apply discount to entire purchase." +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/discounts/add-discount.php:115 +#: includes/admin/discounts/edit-discount.php:148 +msgid "Apply discount only to selected %s." +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/discounts/add-discount.php:120 +#: includes/admin/discounts/edit-discount.php:152 +msgid "%s this discount can only be applied to. Leave blank for any." +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/discounts/add-discount.php:129 +msgctxt "Noun: The plural label for the download post type as a placeholder for a dropdown" +msgid "Excluded %s" +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/discounts/add-discount.php:146 +#: includes/admin/discounts/edit-discount.php:184 +msgid "%s this discount cannot be applied to. Leave blank for none." +msgstr "" + +#: includes/admin/discounts/add-discount.php:160 +#: includes/admin/discounts/edit-discount.php:201 +msgid "Start date" +msgstr "" + +#: includes/admin/discounts/add-discount.php:166 +#: includes/admin/discounts/edit-discount.php:207 +msgid "Start Date Hour" +msgstr "" + +#: includes/admin/discounts/add-discount.php:172 +#: includes/admin/discounts/edit-discount.php:213 +msgid "Start Date Minute" +msgstr "" + +#: includes/admin/discounts/add-discount.php:177 +#: includes/admin/discounts/edit-discount.php:218 +msgid "Pick the date and time this discount will start on. Leave blank for no start date." +msgstr "" + +#: includes/admin/discounts/add-discount.php:185 +#: includes/admin/discounts/edit-discount.php:226 +msgid "Expiration date" +msgstr "" + +#: includes/admin/discounts/add-discount.php:191 +#: includes/admin/discounts/edit-discount.php:232 +msgid "Expiration Date Hour" +msgstr "" + +#: includes/admin/discounts/add-discount.php:197 +#: includes/admin/discounts/edit-discount.php:238 +msgid "Expiration Date Minute" +msgstr "" + +#: includes/admin/discounts/add-discount.php:202 +#: includes/admin/discounts/edit-discount.php:243 +msgid "Pick the date and time this discount will expire on. Leave blank to never expire." +msgstr "" + +#: includes/admin/discounts/add-discount.php:210 +#: includes/admin/discounts/edit-discount.php:251 +msgid "Minimum Amount" +msgstr "" + +#: includes/admin/discounts/add-discount.php:213 +#: includes/admin/discounts/edit-discount.php:254 +msgid "No minimum" +msgstr "" + +#: includes/admin/discounts/add-discount.php:214 +#: includes/admin/discounts/edit-discount.php:255 +msgid "The minimum subtotal of item prices in a cart before this discount may be applied." +msgstr "" + +#: includes/admin/discounts/add-discount.php:222 +#: includes/admin/discounts/edit-discount.php:263 +msgid "Max Uses" +msgstr "" + +#. translators: %s: The maximum number of uses of the discount +#: includes/admin/discounts/add-discount.php:225 +#: includes/admin/discounts/edit-discount.php:266 +#: includes/class-edd-cli.php:572 +msgid "Unlimited" +msgstr "" + +#: includes/admin/discounts/add-discount.php:226 +#: includes/admin/discounts/edit-discount.php:267 +msgid "The maximum number of times this discount can be used." +msgstr "" + +#: includes/admin/discounts/add-discount.php:234 +#: includes/admin/discounts/edit-discount.php:275 +msgid "Use Once Per Customer" +msgstr "" + +#: includes/admin/discounts/add-discount.php:241 +#: includes/admin/discounts/edit-discount.php:283 +msgid "Prevent customers from using this discount more than once." +msgstr "" + +#: includes/admin/discounts/add-discount.php:268 +msgid "Add Discount Code" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:80 +#: includes/admin/discounts/edit-discount.php:295 +#: includes/admin/payments/class-payments-table.php:429 +#: includes/admin/payments/orders.php:1118 +#: 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:83 +#: includes/admin/tools.php:584 +#: includes/privacy-functions.php:834 +#: src/Admin/Emails/ListTable.php:81 +msgid "Status" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:83 +#: includes/reports/data/discounts/class-top-five-discounts-list-table.php:77 +msgid "Uses" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:84 +msgid "Start Date" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:85 +msgid "End Date" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:235 +#: includes/admin/discounts/class-discount-codes-table.php:449 +#: 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 +msgid "Deactivate" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:250 +#: includes/admin/discounts/class-discount-codes-table.php:448 +#: includes/admin/views/tmpl-tax-rates-table-bulk-actions.php:19 +#: includes/admin/views/tmpl-tax-rates-table-row.php:51 +#: src/Admin/Promos/About.php:841 +#: src/Licensing/Traits/Controls.php:111 +msgid "Activate" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:266 +#: includes/admin/discounts/class-discount-codes-table.php:450 +msgid "Archive" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:291 +msgid "View Orders" +msgstr "" + +#. translators: %s: Discount code title (name) +#: includes/admin/discounts/class-discount-codes-table.php:337 +msgctxt "Noun: The discount code title (name)" +msgid "Select %s" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:380 +#: includes/admin/payments/orders.php:1058 +msgid "Scheduled" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:386 +msgid "100% Claimed" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:437 +#: 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 "" + +#. translators: %s: Documentation URL +#: includes/admin/discounts/contextual-help.php:46 +#: includes/admin/downloads/contextual-help.php:51 +#: includes/admin/payments/contextual-help.php:58 +#: includes/admin/reporting/contextual-help.php:51 +#: includes/admin/settings/contextual-help.php:52 +msgid "Visit the documentation on the Easy Digital Downloads website." +msgstr "" + +#. translators: %s: Upgrade URL +#: includes/admin/discounts/contextual-help.php:49 +#: includes/admin/downloads/contextual-help.php:54 +#: includes/admin/payments/contextual-help.php:61 +#: 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:58 +#: includes/admin/settings/contextual-help.php:64 +#: includes/admin/settings/register-settings.php:496 +#: includes/admin/settings/register-settings.php:553 +#: includes/admin/settings/register-settings.php:562 +#: includes/admin/settings/register-settings.php:569 +#: includes/admin/settings/register-settings.php:575 +#: includes/admin/settings/register-settings.php:582 +#: includes/admin/settings/register-settings.php:604 +#: includes/gateways/stripe/includes/admin/admin-filters.php:61 +#: src/Admin/Sections.php:99 +#: src/Admin/Settings/Screen.php:183 +#: src/Admin/Tools/Screen.php:69 +msgid "General" +msgstr "" + +#: includes/admin/discounts/contextual-help.php:60 +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:61 +msgid "Discount codes that are set to \"inactive\" cannot be redeemed." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:62 +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:63 +msgid "Discount codes that have already been used cannot be deleted for data integrity and reporting purposes." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:70 +msgid "Adding Discounts" +msgstr "" + +#: includes/admin/discounts/contextual-help.php:72 +msgid "You can create any number of discount codes easily from this page." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:73 +msgid "Discount codes have several options:" +msgstr "" + +#: includes/admin/discounts/contextual-help.php:75 +msgid "Name - this is the name given to the discount. Used primarily for administrative purposes." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:76 +msgid "Code - this is the unique code that customers will enter during checkout to redeem the code." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:77 +msgid "Type - this is the type of discount this code awards." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:78 +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:79 +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:80 +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:81 +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:82 +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:83 +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:84 +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:85 +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:420 +#: includes/admin/discounts/discount-actions.php:451 +msgid "You do not have permission to create discount codes" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:190 +#: includes/admin/discounts/discount-actions.php:388 +msgid "You do not have permission to edit discount codes" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:195 +#: includes/admin/discounts/discount-actions.php:355 +msgid "No discount ID supplied" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:204 +msgid "Invalid discount" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:350 +msgid "You do not have permission to delete discount codes" +msgstr "" + +#: includes/admin/discounts/discount-codes.php:30 +msgid "You do not have permission to edit discounts." +msgstr "" + +#: includes/admin/discounts/discount-codes.php:39 +#: includes/admin/discounts/discount-codes.php:57 +msgid "You do not have permission to manage discounts." +msgstr "" + +#: includes/admin/discounts/discount-codes.php:68 +#: src/HTML/Elements.php:271 +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 +msgid "Edit Discount" +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/discounts/edit-discount.php:163 +msgctxt "Noun: Downloads plural label" +msgid "Excluded %s" +msgstr "" + +#: includes/admin/discounts/edit-discount.php:301 +#: includes/class-edd-discount.php:703 +msgid "Archived" +msgstr "" + +#: includes/admin/discounts/edit-discount.php:303 +msgid "The status of this discount code." +msgstr "" + +#: includes/admin/discounts/edit-discount.php:311 +msgid "Discount Notes" +msgstr "" + +#: includes/admin/discounts/edit-discount.php:342 +msgid "Update Discount Code" +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/discounts/views/categories.php:14 +#: includes/admin/reporting/export/class-batch-export-downloads.php:49 +#: includes/post-types.php:228 +msgid "Categories" +msgstr "" + +#: includes/admin/discounts/views/categories.php:35 +msgid "Only discount products in these categories" +msgstr "" + +#: includes/admin/discounts/views/categories.php:36 +msgid "Do not discount products in these categories" +msgstr "" + +#: includes/admin/discounts/views/categories.php:41 +msgid "Optionally include/exclude products from this discount by category. Leave blank for any." +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/contextual-help.php:64 +msgid "%s Settings" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:66 +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:67 +msgid "Accounting Options - If enabled, define an individual SKU or product number for this download." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:68 +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 "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/contextual-help.php:76 +msgid "%s Prices" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:78 +msgid "Enable variable pricing - By enabling variable pricing, multiple download options and prices can be configured." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:79 +msgid "Enable multi-option purchases - By enabling multi-option purchases customers can add multiple variable price items to their cart at once." +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/contextual-help.php:87 +msgid "%s Files" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:89 +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:90 +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 "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/contextual-help.php:98 +msgid "%s Instructions" +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/contextual-help.php:100 +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:120 +msgid "Purchase Shortcode" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:122 +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:125 +msgid "The ID of a specific download to purchase." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:126 +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:127 +msgid "The text to be displayed on the button or link." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:128 +msgid "button | text - The style of the purchase link." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:130 +msgid "One or more custom CSS classes you want applied to the button." +msgstr "" + +#. translators: 1: Shortcodes Codex URL, 2: EDD Documentation URL +#: includes/admin/downloads/contextual-help.php:134 +msgid "For more information, see using Shortcodes on the WordPress.org Codex or Easy Digital Downloads Documentation" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:35 +msgid "Net Sales" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:36 +#: includes/emails/email-summary/template-parts/data-listing.php:40 +msgid "Net Revenue" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:190 +msgid "You do not have permission to view this data." +msgstr "" + +#. translators: %s: Download Category taxonomy name +#: includes/admin/downloads/dashboard-columns.php:244 +#: src/HTML/CategorySelect.php:70 +#: src/HTML/Elements.php:356 +msgctxt "plural: Example: \"All Categories\"" +msgid "All %s" +msgstr "" + +#. translators: %s: Download Category taxonomy name +#: includes/admin/downloads/dashboard-columns.php:270 +#: includes/admin/downloads/dashboard-columns.php:273 +#: src/HTML/CategorySelect.php:74 +#: src/HTML/CategorySelect.php:77 +msgctxt "plural: Example: \"Search Download Categories\"" +msgid "Search %s" +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/dashboard-columns.php:328 +msgid "%s Configuration" +msgstr "" + +#. translators: %1$s is the singular label, %2$s is the file ID. +#: includes/admin/downloads/metabox.php:335 +msgid "%1$s file ID: %2$s" +msgstr "" + +#: includes/admin/downloads/metabox.php:350 +#: includes/admin/downloads/views/metabox-bundled-products.php:61 +#: src/Admin/Downloads/Editor/VariablePrices.php:219 +msgid "Move up" +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/metabox.php:356 +#: includes/admin/downloads/views/metabox-bundled-products.php:67 +msgid "Move %s up" +msgstr "" + +#: includes/admin/downloads/metabox.php:360 +#: includes/admin/downloads/views/metabox-bundled-products.php:71 +#: src/Admin/Downloads/Editor/VariablePrices.php:229 +msgid "Move down" +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/metabox.php:366 +#: includes/admin/downloads/views/metabox-bundled-products.php:77 +msgid "Move %s down" +msgstr "" + +#. translators: %s: file ID. +#: includes/admin/downloads/metabox.php:375 +msgid "Remove file %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:385 +msgid "File Name" +msgstr "" + +#: includes/admin/downloads/metabox.php:396 +msgid "My Neat File" +msgstr "" + +#: includes/admin/downloads/metabox.php:406 +msgid "File URL" +msgstr "" + +#: includes/admin/downloads/metabox.php:415 +msgid "Enter, upload, choose from Media Library" +msgstr "" + +#: includes/admin/downloads/metabox.php:422 +#: includes/admin/downloads/metabox.php:424 +msgid "Select Files" +msgstr "" + +#: includes/admin/downloads/metabox.php:422 +msgid "Select" +msgstr "" + +#: includes/admin/downloads/metabox.php:445 +#: includes/admin/downloads/metabox.php:448 +msgid "Price Assignment" +msgstr "" + +#: includes/admin/downloads/metabox.php:449 +msgid "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 "" + +#. translators: %s: Download singular label, in lowercase form. +#: includes/admin/downloads/metabox.php:505 +msgid "Insert into %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:537 +msgid "Refund Status" +msgstr "" + +#: includes/admin/downloads/metabox.php:540 +#: includes/refund-functions.php:26 +msgid "Refundable" +msgstr "" + +#: includes/admin/downloads/metabox.php:541 +msgid "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 "" + +#. translators: %s: Default refund status +#: includes/admin/downloads/metabox.php:559 +msgctxt "Download refund status" +msgid "Default (%s)" +msgstr "" + +#: includes/admin/downloads/metabox.php:577 +#: src/Admin/Settings/Tabs/Gateways.php:160 +msgid "Refund Window" +msgstr "" + +#: includes/admin/downloads/metabox.php:581 +msgctxt "refund window interval" +msgid "Days" +msgstr "" + +#: includes/admin/downloads/metabox.php:584 +#: includes/admin/downloads/metabox.php:635 +msgid "Leave blank to use global setting. Enter 0 for unlimited" +msgstr "" + +#: includes/admin/downloads/metabox.php:621 +#: includes/admin/downloads/metabox.php:624 +#: includes/admin/reporting/export/class-batch-export-downloads.php:53 +#: includes/admin/tools.php:890 +#: src/Admin/Settings/Tabs/Misc.php:187 +msgid "File Download Limit" +msgstr "" + +#: includes/admin/downloads/metabox.php:625 +msgid "Limit the number of times a customer who purchased this product can access their download links." +msgstr "" + +#: includes/admin/downloads/metabox.php:671 +msgid "This product is non-taxable" +msgstr "" + +#: includes/admin/downloads/metabox.php:678 +msgid "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:711 +msgid "Disable quantity input for this product" +msgstr "" + +#: includes/admin/downloads/metabox.php:718 +msgid "If disabled, customers will not be provided an option to change the number they wish to purchase." +msgstr "" + +#: includes/admin/downloads/metabox.php:747 +msgid "Enter an SKU for this product." +msgstr "" + +#: includes/admin/downloads/metabox.php:750 +#: includes/admin/reporting/export/class-batch-export-downloads.php:55 +#: includes/emails/tags.php:209 +#: includes/emails/tags.php:352 +#: templates/shortcode-receipt.php:206 +msgid "SKU" +msgstr "" + +#: includes/admin/downloads/metabox.php:751 +msgid "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:800 +msgid "By default, the buy button will be displayed at the bottom of the download. Disable the default buy button and use the EDD Buy Button block to place the button where you prefer." +msgstr "" + +#: includes/admin/downloads/metabox.php:803 +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:810 +#: includes/admin/downloads/metabox.php:813 +msgid "Buy Buttons" +msgstr "" + +#: includes/admin/downloads/metabox.php:823 +msgid "Copy Buy Button Block" +msgstr "" + +#: includes/admin/downloads/metabox.php:827 +msgid "Copy Buy Button Shortcode" +msgstr "" + +#: includes/admin/downloads/metabox.php:831 +#: src/Admin/Downloads/Editor/VariablePrices.php:316 +msgid "Copy Add to Cart Link" +msgstr "" + +#: includes/admin/downloads/metabox.php:844 +msgid "Hide default purchase button." +msgstr "" + +#: includes/admin/downloads/metabox.php:850 +msgid "Buy Button Block Detected" +msgstr "" + +#: includes/admin/downloads/metabox.php:851 +msgid "The Buy Button block is in the post content, so we recommend disabling the default purchase button." +msgstr "" + +#: includes/admin/downloads/metabox.php:864 +msgid "Purchase button behavior" +msgstr "" + +#: includes/admin/downloads/metabox.php:874 +#: includes/admin/thickbox.php:126 +#: src/Admin/Settings/Tabs/Misc.php:143 +msgid "Add to Cart" +msgstr "" + +#: includes/admin/downloads/metabox.php:875 +#: includes/blocks/includes/downloads/downloads.php:162 +#: includes/gateways/stripe/includes/payment-methods/buy-now/template.php:36 +#: includes/shortcodes.php:47 +#: includes/template-functions.php:92 +#: src/Admin/Settings/Tabs/Misc.php:253 +#: includes/blocks/build/buy-button/index.js:13 +#: includes/blocks/src/buy-button/edit.js:71 +msgid "Buy Now" +msgstr "" + +#: includes/admin/downloads/metabox.php:913 +#: src/Admin/Downloads/Editor/Notes.php:75 +msgid "Download Instructions" +msgstr "" + +#. translators: %s: singular label. +#: includes/admin/downloads/metabox.php:921 +#: src/Admin/Downloads/Editor/Notes.php:83 +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/views/metabox-bundled-products.php:53 +msgid "Select a product" +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/downloads/views/metabox-bundled-products.php:83 +msgctxt "Noun: The singular label for the download post type" +msgid "Select %s:" +msgstr "" + +#: includes/admin/downloads/views/metabox-bundled-products.php:103 +msgid "Price assignment:" +msgstr "" + +#. translators: %s: The bundle product index number. +#: includes/admin/downloads/views/metabox-bundled-products.php:137 +msgid "Remove bundle option %s" +msgstr "" + +#. translators: %s: Singular label for the download post type +#: includes/admin/downloads/views/metabox-bundled-products.php:150 +msgid "Add New %s" +msgstr "" + +#: includes/admin/downloads/views/metabox-files.php:47 +msgid "Add New File" +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:67 +#: includes/admin/import/class-batch-import-payments.php:77 +#: includes/admin/import/class-batch-import.php:206 +msgid "You do not have permission to import data." +msgstr "" + +#: includes/admin/import/class-batch-import-payments.php:291 +#: includes/admin/reporting/export/class-batch-export-payments.php:53 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:70 +msgid "Products (Raw)" +msgstr "" + +#: includes/admin/import/class-batch-import-payments.php:648 +msgid "payments" +msgstr "" + +#: includes/admin/import/import-functions.php:31 +#: includes/admin/import/import-functions.php:112 +msgid "Missing import parameters. Import class must be specified." +msgstr "" + +#: includes/admin/import/import-functions.php:43 +#: includes/admin/import/import-functions.php:140 +msgid "Invalid importer class supplied" +msgstr "" + +#: includes/admin/import/import-functions.php:52 +#: includes/admin/import/import-functions.php:150 +msgid "You do not have permission to import data" +msgstr "" + +#: includes/admin/import/import-functions.php:56 +msgid "Missing import file. Please provide an import file." +msgstr "" + +#: includes/admin/import/import-functions.php:60 +#: includes/admin/import/import-functions.php:133 +msgid "The file you uploaded does not appear to be a CSV file." +msgstr "" + +#: includes/admin/import/import-functions.php:64 +#: includes/admin/import/import-functions.php:116 +msgid "Something went wrong during the upload process, please try again." +msgstr "" + +#: includes/admin/import/import-functions.php:176 +msgid "No data found for import parameters" +msgstr "" + +#. translators: 1: URL to view imported items, 2: Import type label +#: includes/admin/import/import-functions.php:185 +msgid "Import complete! View imported %2$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:127 +msgid "New Customers require a name and email address" +msgstr "" + +#: includes/admin/payments/actions.php:146 +msgid "Error creating new customer" +msgstr "" + +#. translators: %s: email address +#: includes/admin/payments/actions.php:150 +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:215 +msgid "Error updating order." +msgstr "" + +#: includes/admin/payments/actions.php:253 +msgid "You do not have permission to edit this order." +msgstr "" + +#: includes/admin/payments/actions.php:395 +#: includes/admin/payments/actions.php:491 +msgid "You must be logged in to perform this action." +msgstr "" + +#: includes/admin/payments/actions.php:405 +#: includes/admin/payments/actions.php:496 +msgid "Your account does not have permission to perform this action." +msgstr "" + +#: includes/admin/payments/actions.php:416 +msgid "Invalid order ID" +msgstr "" + +#: includes/admin/payments/actions.php:426 +msgid "Invalid order" +msgstr "" + +#: includes/admin/payments/actions.php:435 +msgid "Order is already refunded" +msgstr "" + +#: includes/admin/payments/actions.php:444 +msgid "Cannot refund an order that is already refunded." +msgstr "" + +#: includes/admin/payments/actions.php:455 +msgid "View Refund" +msgstr "" + +#: includes/admin/payments/actions.php:500 +msgid "Missing form data or order ID." +msgstr "" + +#: includes/admin/payments/actions.php:509 +msgid "Nonce validation failed when submitting refund." +msgstr "" + +#: includes/admin/payments/actions.php:556 +msgid "Refund successfully processed." +msgstr "" + +#: includes/admin/payments/actions.php:567 +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:62 +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:168 +msgid "Add Adjustment" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:162 +msgid "All modes" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:176 +msgid "All gateways" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:216 +#: includes/admin/reporting/class-base-logs-list-table.php:504 +#: 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:1386 +msgctxt "date filter" +msgid "From" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:225 +#: includes/admin/reporting/class-base-logs-list-table.php:513 +#: 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:1396 +msgctxt "date filter" +msgid "To" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:255 +msgid "More" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:259 +msgid "Total is" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:262 +msgid "equal to" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:263 +msgid "greater than" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:264 +msgid "less than" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:283 +#: 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:54 +#: includes/blocks/includes/orders/orders.php:554 +#: includes/emails/email-summary/template-parts/top-products.php:17 +msgid "Product" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:298 +msgid "Country & Region" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:323 +msgid "Extras" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:334 +#: includes/admin/reporting/class-base-logs-list-table.php:534 +#: includes/admin/tools/logs.php:238 +#: includes/reports/reports-functions.php:1839 +msgid "Filter" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:338 +#: includes/admin/reporting/class-base-logs-list-table.php:538 +#: src/Admin/Emails/ListTable.php:282 +msgid "Clear" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:357 +#: includes/admin/payments/contextual-help.php:105 +#: includes/admin/reporting/class-base-logs-list-table.php:553 +#: src/Admin/Emails/ListTable.php:286 +msgid "Search" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:390 +msgid "Search orders..." +msgstr "" + +#: includes/admin/payments/class-payments-table.php:406 +msgid "Easy Digital Downloads needs to upgrade the database. Orders will be available when that has completed." +msgstr "" + +#: includes/admin/payments/class-payments-table.php:411 +msgid "No orders found." +msgstr "" + +#. translators: %s: the order number +#: includes/admin/payments/class-payments-table.php:539 +msgctxt "Number: The order ID in alpha numeric representation" +msgid "Select %s" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:576 +#: includes/emails/tags.php:738 +msgid "View Receipt" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:591 +#: includes/admin/payments/orders.php:404 +msgid "Resend Receipt" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:606 +msgid "Trash" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:618 +#: includes/admin/payments/class-payments-table.php:747 +msgid "Restore" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:630 +msgid "Delete Permanently" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:733 +msgid "Mark Completed" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:734 +msgid "Mark Pending" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:735 +msgid "Mark Processing" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:736 +msgid "Mark Revoked" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:737 +msgid "Mark Failed" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:738 +msgid "Mark Abandoned" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:739 +msgid "Resend Receipts" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:751 +msgid "Delete permanently" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:754 +#: includes/admin/payments/orders.php:1217 +#: includes/admin/payments/refunds.php:308 +msgid "Move to Trash" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:1170 +#: includes/admin/payments/class-payments-table.php:1173 +msgid "Confirmation Required" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:1171 +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:1174 +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:852 +#: includes/admin/payments/refunds.php:230 +#: 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:854 +#: includes/admin/payments/refunds.php:231 +#: includes/admin/reporting/export/class-batch-export-sales.php:60 +#: includes/admin/views/tmpl-order-form-add-order-item.php:110 +#: includes/admin/views/tmpl-order-item.php:73 +#: includes/emails/tags.php:205 +#: includes/emails/tags.php:348 +#: templates/shortcode-receipt.php:209 +msgid "Quantity" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:80 +#: includes/admin/tools.php:594 +#: includes/admin/views/tmpl-order-subtotal.php:16 +#: includes/blocks/views/checkout/cart/cart-subtotal.php:10 +#: includes/blocks/views/orders/totals.php:44 +#: src/Emails/Tags/Registry.php:119 +#: 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-sales.php:62 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:73 +#: includes/admin/tools.php:603 +#: 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:57 +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:77 +#: src/Emails/Tags/Registry.php:126 +#: 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:50 +msgid "Order Fee" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:159 +#: includes/admin/views/tmpl-order-adjustment.php:48 +#: includes/ajax-functions.php:1107 +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 "" + +#. translators: %s: The product name +#: includes/admin/payments/class-refund-items-table.php:447 +msgctxt "Title: The title of the current download product" +msgid "Select %s" +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:133 +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:70 +#: includes/admin/reporting/reports.php:214 +msgid "Overview" +msgstr "" + +#: includes/admin/payments/contextual-help.php:72 +msgid "This screen provides access to all of the orders and refunds in your store." +msgstr "" + +#: includes/admin/payments/contextual-help.php:73 +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:74 +msgid "To maintain accurate reporting and accounting, we strongly advise against deleting any completed order data." +msgstr "" + +#: includes/admin/payments/contextual-help.php:81 +msgid "— Orders" +msgstr "" + +#: includes/admin/payments/contextual-help.php:83 +msgid "Orders are placed by customers when they buy things from your store." +msgstr "" + +#: includes/admin/payments/contextual-help.php:84 +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:85 +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:86 +msgid "Both full and partial refunds are supported." +msgstr "" + +#: includes/admin/payments/contextual-help.php:93 +msgid "— Refunds" +msgstr "" + +#: includes/admin/payments/contextual-help.php:95 +msgid "Refunds are created when a customer would like money back from a completed order." +msgstr "" + +#: includes/admin/payments/contextual-help.php:96 +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:97 +msgid "Refunds could be entire orders, or single products." +msgstr "" + +#: includes/admin/payments/contextual-help.php:98 +msgid "Once an item is refunded, it cannot be undone; it can only be repurchased." +msgstr "" + +#: includes/admin/payments/contextual-help.php:107 +msgid "The order history can be searched in several different ways." +msgstr "" + +#: includes/admin/payments/contextual-help.php:108 +msgid "You can enter:" +msgstr "" + +#: includes/admin/payments/contextual-help.php:110 +msgid "The specific order ID" +msgstr "" + +#: includes/admin/payments/contextual-help.php:111 +msgid "The 32-character order key" +msgstr "" + +#: includes/admin/payments/contextual-help.php:112 +msgid "The customer's email address" +msgstr "" + +#. translators: %s: the prefix needed to search by customer - This should remain untranslated `customer:` +#: includes/admin/payments/contextual-help.php:115 +msgid "The customer's name or ID prefixed by %s" +msgstr "" + +#. translators: %s: the prefix needed to search by user - This should remain untranslated `user:` +#: includes/admin/payments/contextual-help.php:120 +msgid "A user's ID prefixed by %s" +msgstr "" + +#. translators: %s: the prefix needed to search by Order ID - This should remain untranslated `#` +#: includes/admin/payments/contextual-help.php:125 +msgid "The %1$s ID prefixed by %2$s" +msgstr "" + +#. translators: %s: the prefix needed to search by discount code - This should remain untranslated `discount:` +#: includes/admin/payments/contextual-help.php:131 +msgid "The Discount Code prefixed by %s" +msgstr "" + +#. translators: %s: the prefix needed to search by transaction ID - This should remain untranslated `txn:` +#: includes/admin/payments/contextual-help.php:136 +msgid "A transaction ID prefixed by %s" +msgstr "" + +#. translators: %s: the prefix needed to search by Stripe Payment Method - This should remain untranslated `txn:` +#: includes/admin/payments/contextual-help.php:141 +msgid "A Stripe Payment Method prefixed by %s" +msgstr "" + +#: includes/admin/payments/contextual-help.php:151 +#: src/Admin/Downloads/Editor/Details.php:54 +#: templates/history-purchases.php:54 +msgid "Details" +msgstr "" + +#: includes/admin/payments/contextual-help.php:153 +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:156 +msgid "The file associated with the purchase." +msgstr "" + +#: includes/admin/payments/contextual-help.php:157 +msgid "The exact date and time the order was completed." +msgstr "" + +#: includes/admin/payments/contextual-help.php:158 +msgid "If a coupon or discount was used during the checkout process." +msgstr "" + +#: includes/admin/payments/contextual-help.php:159 +msgid "The buyer's name." +msgstr "" + +#: includes/admin/payments/contextual-help.php:160 +msgid "The buyer's email address." +msgstr "" + +#: includes/admin/payments/contextual-help.php:161 +msgid "Any customer-specific notes related to the order." +msgstr "" + +#: includes/admin/payments/contextual-help.php:162 +msgid "The name of the order gateway used to complete the order." +msgstr "" + +#: includes/admin/payments/contextual-help.php:163 +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:48 +msgid "Checking this box will email the purchase receipt to the selected customer." +msgstr "" + +#: includes/admin/payments/orders.php:193 +msgid "Assign" +msgstr "" + +#: includes/admin/payments/orders.php:194 +msgid "Switch Customer" +msgstr "" + +#: includes/admin/payments/orders.php:204 +msgid "Select existing customer" +msgstr "" + +#: includes/admin/payments/orders.php:205 +msgid "Create new customer" +msgstr "" + +#: includes/admin/payments/orders.php:211 +msgid "Select customer" +msgstr "" + +#: includes/admin/payments/orders.php:220 +#: includes/admin/payments/orders.php:221 +msgid "Search for a customer" +msgstr "" + +#: includes/admin/payments/orders.php:254 +msgid "View customer record" +msgstr "" + +#: includes/admin/payments/orders.php:265 +#: 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:61 +#: includes/admin/tools.php:465 +#: src/Emails/Tags/Registry.php:77 +#: templates/shortcode-profile-editor.php:44 +msgid "First Name" +msgstr "" + +#: includes/admin/payments/orders.php:275 +#: 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:62 +#: includes/admin/tools.php:475 +#: templates/shortcode-profile-editor.php:49 +msgid "Last Name" +msgstr "" + +#. translators: email type +#: includes/admin/payments/orders.php:332 +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:333 +msgid "selected" +msgstr "" + +#: includes/admin/payments/orders.php:333 +msgid "customer" +msgstr "" + +#: includes/admin/payments/orders.php:337 +msgid "Sending purchase receipts from Easy Digital Downloads has been disabled." +msgstr "" + +#: includes/admin/payments/orders.php:341 +msgid "Send email receipt to" +msgstr "" + +#: includes/admin/payments/orders.php:365 +msgid "Customer Primary" +msgstr "" + +#: includes/admin/payments/orders.php:367 +msgid "Order Email" +msgstr "" + +#: includes/admin/payments/orders.php:441 +#: includes/blocks/views/checkout/purchase-form/address.php:3 +#: includes/privacy-functions.php:824 +#: src/Emails/Tags/Registry.php:105 +msgid "Billing Address" +msgstr "" + +#: includes/admin/payments/orders.php:444 +msgid "Existing Address:" +msgstr "" + +#: includes/admin/payments/orders.php:449 +msgid "Line 1:" +msgstr "" + +#: includes/admin/payments/orders.php:456 +msgid "Line 2:" +msgstr "" + +#: includes/admin/payments/orders.php:463 +msgctxt "Address City" +msgid "City:" +msgstr "" + +#: includes/admin/payments/orders.php:470 +msgctxt "Zip / Postal code of address" +msgid "Zip / Postal Code:" +msgstr "" + +#: includes/admin/payments/orders.php:477 +msgctxt "Address country" +msgid "Country:" +msgstr "" + +#: includes/admin/payments/orders.php:489 +msgid "Search Countries" +msgstr "" + +#: includes/admin/payments/orders.php:499 +msgctxt "Region of address" +msgid "Region:" +msgstr "" + +#: includes/admin/payments/orders.php:511 +msgid "Search Regions" +msgstr "" + +#: includes/admin/payments/orders.php:597 +msgid "File Download Log for Order" +msgstr "" + +#: includes/admin/payments/orders.php:598 +msgid "Customer Download Log" +msgstr "" + +#: includes/admin/payments/orders.php:599 +#: includes/privacy-functions.php:852 +msgid "Customer Orders" +msgstr "" + +#: includes/admin/payments/orders.php:616 +msgid "Recalculate Order Values" +msgstr "" + +#: includes/admin/payments/orders.php:819 +#: includes/admin/views/tmpl-order-copy-download-link.php:38 +#: includes/gateways/stripe/includes/utils/modal.php:69 +#: src/Admin/Pointers.php:42 +msgid "Close" +msgstr "" + +#: includes/admin/payments/orders.php:884 +msgid "Order Details" +msgstr "" + +#: includes/admin/payments/orders.php:933 +msgid "Order Extras" +msgstr "" + +#: includes/admin/payments/orders.php:971 +msgid "Key" +msgstr "" + +#: includes/admin/payments/orders.php:981 +#: includes/admin/payments/orders.php:989 +msgid "IP" +msgstr "" + +#: includes/admin/payments/orders.php:996 +#: includes/admin/payments/orders.php:1019 +#: 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:76 +#: includes/admin/tools.php:622 +#: src/Emails/Tags/Registry.php:233 +msgid "Transaction ID" +msgstr "" + +#: includes/admin/payments/orders.php:1008 +msgid "Dispute ID" +msgstr "" + +#: includes/admin/payments/orders.php:1033 +msgid "Unlimited Downloads" +msgstr "" + +#: includes/admin/payments/orders.php:1037 +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:1048 +msgid "Deferred Actions" +msgstr "" + +#: includes/admin/payments/orders.php:1051 +msgid "Not Run" +msgstr "" + +#: includes/admin/payments/orders.php:1055 +#: includes/payments/functions.php:539 +msgid "Completed" +msgstr "" + +#: includes/admin/payments/orders.php:1072 +msgid "Deferred Actions were added in Easy Digital Downloads 2.8. Orders placed on prior versions will not have a deferred actions status. If this order was placed on a version of Easy Digital Downloads supporting Deferred Actions, please verify that WP Cron is able to be run." +msgstr "" + +#: includes/admin/payments/orders.php:1109 +msgid "Order Attributes" +msgstr "" + +#: includes/admin/payments/orders.php:1124 +msgid "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:1127 +msgid "Complete" +msgstr "" + +#: includes/admin/payments/orders.php:1128 +msgid "All processing is completed for this purchase." +msgstr "" + +#: includes/admin/payments/orders.php:1131 +#: includes/payments/functions.php:542 +msgid "Revoked" +msgstr "" + +#: includes/admin/payments/orders.php:1132 +msgid "Access to purchased items is disabled, perhaps due to policy violation or fraud." +msgstr "" + +#: includes/admin/payments/orders.php:1135 +#: includes/payments/functions.php:540 +msgid "Refunded" +msgstr "" + +#: includes/admin/payments/orders.php:1136 +msgid "The purchase amount is returned to the customer and access to items is disabled." +msgstr "" + +#: includes/admin/payments/orders.php:1139 +#: includes/payments/functions.php:544 +msgid "Abandoned" +msgstr "" + +#: includes/admin/payments/orders.php:1140 +msgid "The purchase attempt was not completed by the customer." +msgstr "" + +#: includes/admin/payments/orders.php:1143 +#: includes/payments/functions.php:543 +msgid "Failed" +msgstr "" + +#: includes/admin/payments/orders.php:1144 +msgid "Customer clicked Cancel before completing the purchase." +msgstr "" + +#: includes/admin/payments/orders.php:1147 +#: includes/payments/functions.php:545 +msgid "On Hold" +msgstr "" + +#: includes/admin/payments/orders.php:1148 +msgid "Order is held for review. Order items are not available to download." +msgstr "" + +#: includes/admin/payments/orders.php:1187 +msgid "On Hold Due To:" +msgstr "" + +#: includes/admin/payments/orders.php:1188 +msgid "Original Hold Reason:" +msgstr "" + +#: includes/admin/payments/orders.php:1228 +msgid "Recover" +msgstr "" + +#: includes/admin/payments/orders.php:1231 +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:1257 +msgid "Time" +msgstr "" + +#: includes/admin/payments/orders.php:1262 +msgid "Hour" +msgstr "" + +#: includes/admin/payments/orders.php:1268 +msgid "Minute" +msgstr "" + +#: includes/admin/payments/payments-history.php:207 +msgid "Edit Order" +msgstr "" + +#: includes/admin/payments/payments-history.php:212 +msgid "Add New Order" +msgstr "" + +#: includes/admin/payments/refunds.php:41 +msgid "View Original Order" +msgstr "" + +#: includes/admin/payments/refunds.php:47 +msgid "You are viewing a refund record." +msgstr "" + +#: includes/admin/payments/refunds.php:50 +msgid "A refund is a read-only record to help balance your store's books." +msgstr "" + +#: includes/admin/payments/refunds.php:253 +msgid "Refund Notes" +msgstr "" + +#: includes/admin/payments/refunds.php:297 +msgid "Refund Attributes" +msgstr "" + +#: includes/admin/payments/refunds.php:315 +msgid "Original Order" +msgstr "" + +#: includes/admin/payments/refunds.php:349 +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 "" + +#. translators: %s: refund link +#: includes/admin/payments/view-order-details.php:43 +msgid "The specified ID is for a refund, not an order. Please access the refund directly." +msgstr "" + +#. translators: %s: order number +#: includes/admin/payments/view-order-details.php:57 +#: includes/blocks/views/orders/orders.php:19 +msgid "Order: %s" +msgstr "" + +#. translators: %s: singular label +#: includes/admin/payments/view-order-details.php:138 +#: 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/plugins.php:48 +#: src/Admin/Downloads/Editor/Settings.php:54 +#: src/Admin/Emails/Screen.php:35 +#: src/Admin/Menu/Header.php:145 +#: src/Admin/Menu/Pages.php:82 +#: includes/blocks/build/buy-button/index.js:12 +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/build/confirmation/index.js:1 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/receipt/index.js:1 +#: includes/blocks/build/register/index.js:1 +#: includes/blocks/src/buy-button/edit.js:55 +#: includes/blocks/src/cart/edit.js:27 +#: includes/blocks/src/confirmation/edit.js:26 +#: includes/blocks/src/login/edit.js:23 +#: includes/blocks/src/receipt/edit.js:26 +#: includes/blocks/src/register/edit.js:22 +msgid "Settings" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:38 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:125 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:87 +#: 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:39 +msgid "Request Details" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:40 +#: 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:41 +msgid "Request IP" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:42 +#: 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:68 +msgid "View Request" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:70 +msgid "API Request:" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:80 +msgid "API User:" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:82 +msgid "API Key:" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:84 +msgid "Request Date:" +msgstr "" + +#. translators: %d: customer ID +#: includes/admin/reporting/class-base-logs-list-table.php:529 +msgid "Customer ID: %d" +msgstr "" + +#: includes/admin/reporting/class-base-logs-list-table.php:587 +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/payment-gateways/class-gateway-stats-list-table.php:82 +#: src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php:179 +#: src/Reports/Data/Gateways/StripePaymentMethods.php:81 +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:241 +#: includes/admin/reporting/reports.php:400 +#: includes/admin/reporting/reports.php:408 +#: includes/admin/reporting/reports.php:707 +#: includes/admin/reporting/reports.php:928 +#: includes/admin/reporting/reports.php:1026 +#: includes/admin/reporting/reports.php:1500 +#: includes/admin/reporting/reports.php:1711 +#: includes/admin/reporting/reports.php:1912 +#: includes/admin/reporting/reports.php:2008 +#: 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 +#: includes/blocks/src/utilities/download-order-by.js:22 +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:51 +msgid "Downloaded by" +msgstr "" + +#: includes/admin/reporting/class-export-download-history.php:61 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:130 +#: includes/admin/reporting/export/class-batch-export-api-requests.php:41 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:52 +#: includes/admin/reporting/export/class-batch-export-payments.php:64 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:81 +#: includes/admin/tools.php:505 +#: includes/privacy-functions.php:828 +#: includes/privacy-functions.php:921 +#: includes/privacy-functions.php:999 +#: src/Emails/Tags/Registry.php:203 +msgid "IP Address" +msgstr "" + +#: includes/admin/reporting/class-export-download-history.php:63 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:129 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:55 +msgid "File" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:56 +#: includes/admin/reporting/class-export.php:83 +#: 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:5 +#: includes/blocks/src/terms/edit.js:31 +#: includes/blocks/src/utilities/download-order-by.js:14 +msgid "ID" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:57 +#: includes/admin/tools.php:545 +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:64 +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:66 +#: includes/gateways/stripe/includes/template-functions.php:611 +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:68 +#: includes/admin/tools.php:680 +#: includes/blocks/views/checkout/purchase-form/address.php:105 +#: includes/checkout/template.php:403 +#: includes/gateways/stripe/includes/template-functions.php:889 +#: 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:332 +#: src/Admin/Onboarding/Wizard.php:254 +#: 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:71 +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:63 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:80 +#: includes/admin/settings/register-settings.php:545 +#: src/Admin/Settings/Tabs/General.php:177 +#: src/Admin/Settings/Tabs/General.php:185 +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:74 +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:535 +#: includes/blocks/views/orders/totals.php:33 +#: src/Emails/Tags/Registry.php:168 +#: src/Reports/Data/Gateways/StripePaymentMethods.php:77 +#: 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:77 +#: includes/admin/tools.php:564 +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:55 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:79 +#: includes/admin/tools.php:632 +#: src/Emails/Templates/Registry.php:80 +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:145 +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:228 +msgid "guest" +msgstr "" + +#: includes/admin/reporting/class-export.php:196 +#: includes/admin/reporting/export/class-batch-export.php:178 +#: 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:128 +msgid "You do not have permission to export data." +msgstr "" + +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:128 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:88 +#: 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:58 +msgid "Order Number" +msgstr "" + +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:131 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:53 +msgid "User Agent" +msgstr "" + +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:54 +msgid "View Log Message" +msgstr "" + +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:68 +msgid "Log data:" +msgstr "" + +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:90 +msgid "Error Message" +msgstr "" + +#. translators: %s: payment data +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:117 +#: includes/gateways/manual.php:72 +#: includes/gateways/paypal-standard.php:197 +msgid "Payment Error" +msgstr "" + +#: includes/admin/reporting/class-gateways-reports-table.php:75 +#: includes/reports/data/payment-gateways/class-gateway-stats-list-table.php:79 +#: src/Reports/Data/Gateways/StripePaymentMethods.php:78 +msgid "Complete Sales" +msgstr "" + +#: includes/admin/reporting/class-gateways-reports-table.php:76 +#: includes/reports/data/payment-gateways/class-gateway-stats-list-table.php:80 +#: src/Reports/Data/Gateways/StripePaymentMethods.php:79 +msgid "Pending / Failed Sales" +msgstr "" + +#: includes/admin/reporting/class-sales-logs-list-table.php:100 +#: includes/admin/reporting/export/class-batch-export-sales.php:61 +msgid "Item Amount" +msgstr "" + +#: includes/admin/reporting/contextual-help.php:61 +#: includes/admin/reporting/reports.php:181 +#: src/Admin/Menu/Header.php:148 +#: src/Admin/Menu/Pages.php:75 +#: src/Admin/Menu/Pages.php:76 +msgid "Reports" +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:3154 +#: includes/admin/reporting/views/export-sales-earnings.php:61 +#: includes/admin/tools.php:955 +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-api-requests.php:43 +msgid "API Key" +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:643 +msgid "Date Created" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:42 +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:39 +msgid "Slug" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:45 +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:85 +msgid "Author" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:46 +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:154 +msgid "Description" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:47 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-content.js:14 +msgid "Excerpt" +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/reporting/export/class-batch-export-downloads.php:50 +#: includes/post-types.php:280 +msgid "Tags" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:52 +#: includes/blocks/includes/orders/orders.php:579 +#: src/Admin/Downloads/Editor/Files.php:64 +#: src/Admin/Downloads/Metabox.php:58 +#: 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-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:64 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:57 +#: includes/privacy-functions.php:913 +msgid "Order ID" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:44 +#: includes/admin/reporting/export/class-batch-export-sales.php:56 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:60 +#: includes/admin/tools.php:485 +#: includes/privacy-functions.php:631 +#: includes/privacy-functions.php:917 +msgid "Customer ID" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:52 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:69 +msgid "Products (Verbose)" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:65 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:82 +#: includes/admin/tools.php:515 +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:84 +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:54 +msgid "Product ID" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-sales.php:65 +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:624 +msgid "Total earnings for period shown: " +msgstr "" + +#: includes/admin/reporting/graphing.php:336 +#: includes/admin/reporting/graphing.php:632 +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 "" + +#. translators: %s: Download title +#: includes/admin/reporting/graphing.php:609 +msgid "Earnings Over Time for %s" +msgstr "" + +#. translators: %s: Formatted currency value for earnings +#: includes/admin/reporting/graphing.php:642 +msgid "Average monthly earnings: %s" +msgstr "" + +#. translators: %s: Number of sales +#: includes/admin/reporting/graphing.php:653 +msgid "Average monthly sales: %s" +msgstr "" + +#: includes/admin/reporting/graphing.php:717 +msgid "Invalid date format. Please enter a date in the format: YYYY-mm-dd." +msgstr "" + +#: includes/admin/reporting/graphing.php:718 +msgid "Invalid Date Error" +msgstr "" + +#: includes/admin/reporting/graphing.php:799 +msgid "Clicking this will clear the reports cache" +msgstr "" + +#: includes/admin/reporting/graphing.php:799 +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:294 +#: includes/admin/reporting/reports.php:1600 +msgid "Average Order Value" +msgstr "" + +#: includes/admin/reporting/reports.php:321 +#: includes/admin/reporting/reports.php:2931 +#: includes/admin/reporting/reports.php:3026 +#: includes/admin/reporting/reports.php:3102 +#: includes/emails/email-summary/template-parts/data-listing.php:68 +msgid "New Customers" +msgstr "" + +#: includes/admin/reporting/reports.php:345 +#: includes/admin/reporting/reports.php:1247 +msgid "Total Refund Amount" +msgstr "" + +#: includes/admin/reporting/reports.php:471 +msgid "Sales / Earnings" +msgstr "" + +#. translators: %s: Download singular label +#: includes/admin/reporting/reports.php:548 +msgid "Most Valuable %s" +msgstr "" + +#: includes/admin/reporting/reports.php:590 +msgid "Average Sales / Earnings" +msgstr "" + +#: includes/admin/reporting/reports.php:607 +msgid "Net\t" +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/admin/reporting/reports.php:657 +msgid "Top Selling %s" +msgstr "" + +#: includes/admin/reporting/reports.php:672 +msgid "Sales by Variation" +msgstr "" + +#: includes/admin/reporting/reports.php:727 +msgid "Earnings by Variation" +msgstr "" + +#. translators: %s: Downloads label +#: includes/admin/reporting/reports.php:1086 +msgid "%s Terms" +msgstr "" + +#: includes/admin/reporting/reports.php:1101 +msgid "Earnings By Term" +msgstr "" + +#: includes/admin/reporting/reports.php:1141 +#: includes/admin/reporting/reports.php:1566 +#: includes/admin/settings/register-settings.php:555 +#: includes/orders/functions/types.php:102 +#: src/Admin/Settings/Tabs/Gateways.php:144 +#: src/Admin/Settings/Tabs/Gateways.php:147 +msgid "Refunds" +msgstr "" + +#: includes/admin/reporting/reports.php:1170 +msgid "Number of Refunds" +msgstr "" + +#: includes/admin/reporting/reports.php:1196 +msgid "Number of Fully Refunded Orders" +msgstr "" + +#: includes/admin/reporting/reports.php:1223 +msgid "Number of Fully Refunded Items" +msgstr "" + +#: includes/admin/reporting/reports.php:1272 +msgid "Average Refund Amount" +msgstr "" + +#: includes/admin/reporting/reports.php:1298 +msgid "Average Time to Refund" +msgstr "" + +#: includes/admin/reporting/reports.php:1321 +msgid "Refund Rate" +msgstr "" + +#: includes/admin/reporting/reports.php:1346 +msgid "Refunded Revenue" +msgstr "" + +#: includes/admin/reporting/reports.php:1354 +msgid "Revenue" +msgstr "" + +#: includes/admin/reporting/reports.php:1373 +msgid "Refunded Orders" +msgstr "" + +#: includes/admin/reporting/reports.php:1381 +msgctxt "Context: As in the total number of items" +msgid "Number" +msgstr "" + +#: includes/admin/reporting/reports.php:1485 +msgid "Payment Gateways" +msgstr "" + +#: includes/admin/reporting/reports.php:1639 +msgid "Gateway Stats" +msgstr "" + +#: includes/admin/reporting/reports.php:1654 +msgid "Stripe Payment Methods" +msgstr "" + +#: includes/admin/reporting/reports.php:1670 +msgid "Gateway Sales" +msgstr "" + +#: includes/admin/reporting/reports.php:1731 +msgid "Gateway Earnings" +msgstr "" + +#: includes/admin/reporting/reports.php:2095 +#: includes/admin/settings/contextual-help.php:112 +#: includes/admin/settings/register-settings.php:501 +msgid "Taxes" +msgstr "" + +#: includes/admin/reporting/reports.php:2109 +msgid "Total Tax Collected" +msgstr "" + +#: includes/admin/reporting/reports.php:2152 +msgid "Total Tax Collected for " +msgstr "" + +#: includes/admin/reporting/reports.php:2190 +msgid "Tax Collected by Location" +msgstr "" + +#: includes/admin/reporting/reports.php:2267 +#: includes/admin/reporting/reports.php:2482 +#: includes/admin/settings/register-settings.php:606 +#: includes/admin/tools/logs.php:174 +#: includes/privacy-functions.php:591 +#: includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php:58 +msgid "File Downloads" +msgstr "" + +#: includes/admin/reporting/reports.php:2282 +#: includes/admin/reporting/reports.php:2396 +msgid "Number of File Downloads" +msgstr "" + +#: includes/admin/reporting/reports.php:2314 +msgid "Average per Customer" +msgstr "" + +#: includes/admin/reporting/reports.php:2337 +msgid "Average per Order" +msgstr "" + +#: includes/admin/reporting/reports.php:2360 +msgid "Most Downloaded Product" +msgstr "" + +#: includes/admin/reporting/reports.php:2381 +msgid "Top Five Most Downloaded Products" +msgstr "" + +#: includes/admin/reporting/reports.php:2602 +msgid "Number of Discounts Used" +msgstr "" + +#: includes/admin/reporting/reports.php:2624 +msgid "Discount Ratio" +msgstr "" + +#: includes/admin/reporting/reports.php:2647 +msgid "Customer Savings" +msgstr "" + +#: includes/admin/reporting/reports.php:2673 +msgid "Average Discount Amount" +msgstr "" + +#: includes/admin/reporting/reports.php:2696 +msgid "Most Popular Discount" +msgstr "" + +#: includes/admin/reporting/reports.php:2726 +msgid "Discount Usage Count" +msgstr "" + +#: includes/admin/reporting/reports.php:2750 +msgid "Top Five Discounts" +msgstr "" + +#: includes/admin/reporting/reports.php:2766 +#: includes/admin/reporting/reports.php:2846 +msgid "Discount Usage" +msgstr "" + +#: includes/admin/reporting/reports.php:2955 +msgid "Average Revenue per Customer" +msgstr "" + +#: includes/admin/reporting/reports.php:2975 +msgid "Average Orders per Customer" +msgstr "" + +#: includes/admin/reporting/reports.php:2996 +msgid "Top Five Customers — All Time" +msgstr "" + +#: includes/admin/reporting/reports.php:3011 +msgid "Most Valuable Customers" +msgstr "" + +#: includes/admin/reporting/reports.php:3266 +#: includes/user-functions.php:872 +#: includes/user-functions.php:876 +msgid "Notice" +msgstr "" + +#: includes/admin/reporting/reports.php:3267 +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:68 +#: 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 "" + +#. translators: %s: Downloads plural label +#: includes/admin/reporting/views/export-customers.php:8 +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:40 +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:50 +msgid "Select Taxonomy" +msgstr "" + +#: includes/admin/reporting/views/export-customers.php:49 +msgid "All Taxonomies" +msgstr "" + +#: includes/admin/reporting/views/export-customers.php:53 +#: 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: %s: Download plural label +#: includes/admin/reporting/views/export-customers.php:61 +#: 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:1486 +#: src/HTML/ProductSelect.php:52 +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: %s: Download plural 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 +#: src/HTML/Elements.php:503 +msgid "All Regions" +msgstr "" + +#. translators: %s: Upgrade URL +#: includes/admin/settings/contextual-help.php:55 +msgid "Need more from your Easy Digital Downloads store? Upgrade Now!." +msgstr "" + +#: includes/admin/settings/contextual-help.php:65 +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:72 +#: includes/admin/settings/register-settings.php:497 +msgid "Payments" +msgstr "" + +#: includes/admin/settings/contextual-help.php:74 +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:75 +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:76 +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:77 +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:84 +#: includes/admin/settings/register-settings.php:498 +#: src/Admin/Emails/Screen.php:34 +#: src/Admin/Menu/Header.php:163 +#: src/Admin/Menu/Pages.php:88 +#: src/Admin/Onboarding/Wizard.php:242 +msgid "Emails" +msgstr "" + +#: includes/admin/settings/contextual-help.php:86 +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:87 +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:94 +#: includes/admin/settings/register-settings.php:499 +msgid "Marketing" +msgstr "" + +#: includes/admin/settings/contextual-help.php:96 +msgid "Marketing settings will help you connect with your customers." +msgstr "" + +#: includes/admin/settings/contextual-help.php:97 +msgid "Marketing specific extensions will add their settings here as well." +msgstr "" + +#: includes/admin/settings/contextual-help.php:104 +#: includes/admin/settings/register-settings.php:500 +msgid "Styles" +msgstr "" + +#: includes/admin/settings/contextual-help.php:105 +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:114 +msgid "This screen allows you to configure the tax rules for your store." +msgstr "" + +#: includes/admin/settings/contextual-help.php:115 +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:116 +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:117 +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:118 +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:119 +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:120 +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:121 +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:122 +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:123 +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:130 +#: includes/admin/settings/register-settings.php:502 +msgid "Policies" +msgstr "" + +#: includes/admin/settings/contextual-help.php:132 +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:133 +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:141 +msgid "This screen provides access to settings added by most Easy Digital Downloads extensions." +msgstr "" + +#: includes/admin/settings/contextual-help.php:148 +msgid "Miscellaneous" +msgstr "" + +#: includes/admin/settings/contextual-help.php:150 +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:34 +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:56 +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:73 +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:504 +msgid "Licenses" +msgstr "" + +#: includes/admin/settings/register-settings.php:505 +msgid "Misc" +msgstr "" + +#: includes/admin/settings/register-settings.php:544 +msgid "Store" +msgstr "" + +#: includes/admin/settings/register-settings.php:546 +#: src/Admin/Settings/Tabs/General.php:118 +msgid "Pages" +msgstr "" + +#: includes/admin/settings/register-settings.php:547 +#: src/Admin/Settings/Tabs/General.php:225 +msgid "API" +msgstr "" + +#: includes/admin/settings/register-settings.php:554 +#: includes/blocks/includes/checkout/checkout.php:131 +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:89 +#: 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:556 +msgid "Accounting" +msgstr "" + +#: includes/admin/settings/register-settings.php:563 +msgid "Summaries" +msgstr "" + +#: includes/admin/settings/register-settings.php:576 +msgid "Buttons" +msgstr "" + +#: includes/admin/settings/register-settings.php:583 +msgid "Rates" +msgstr "" + +#: includes/admin/settings/register-settings.php:589 +#: src/Admin/Settings/Tabs/Privacy.php:43 +msgid "Privacy Policy" +msgstr "" + +#: includes/admin/settings/register-settings.php:590 +#: src/Admin/Settings/Tabs/Privacy.php:73 +msgid "Terms & Agreements" +msgstr "" + +#: includes/admin/settings/register-settings.php:591 +msgid "Export & Erase" +msgstr "" + +#: includes/admin/settings/register-settings.php:597 +msgid "Main" +msgstr "" + +#: includes/admin/settings/register-settings.php:605 +msgid "Purchase Buttons" +msgstr "" + +#: includes/admin/settings/register-settings.php:629 +#: includes/class-edd-discount.php:691 +#: src/Admin/Settings/Tabs/Gateways.php:391 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +#: includes/blocks/src/utilities/image-alignment.js:6 +msgid "None" +msgstr "" + +#: includes/admin/settings/register-settings.php:995 +#: includes/gateways/functions.php:80 +#: includes/gateways/functions.php:81 +msgid "Store Gateway" +msgstr "" + +#: includes/admin/settings/register-settings.php:996 +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:1008 +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:1027 +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:1027 +msgid "More %1$sPayment Gateways%2$s are available." +msgstr "" + +#: includes/admin/settings/register-settings.php:1056 +msgid "Automatic" +msgstr "" + +#. translators: %s: the setting ID +#: includes/admin/settings/register-settings.php:1290 +msgid "The callback function used for the %s setting is missing." +msgstr "" + +#: includes/admin/settings/register-settings.php:1503 +#: src/Admin/Assets/Localization.php:123 +msgid "Enter a region" +msgstr "" + +#: includes/admin/settings/register-settings.php:1570 +msgid "Recapture plugin activated." +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: includes/admin/settings/register-settings.php:1575 +msgid "%1$sAccess your Recapture account%2$s." +msgstr "" + +#: includes/admin/settings/register-settings.php:1585 +msgid "Disconnect Recapture" +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: includes/admin/settings/register-settings.php:1593 +msgid "%1$sComplete your connection to Recapture%2$s" +msgstr "" + +#: includes/admin/settings/register-settings.php:1607 +msgid "We recommend Recapture for recovering lost revenue by automatically sending effective, targeted emails to customers who abandon their shopping cart." +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: includes/admin/settings/register-settings.php:1612 +msgid "%1$sLearn more%2$s (Free trial available)" +msgstr "" + +#: includes/admin/settings/register-settings.php:1622 +msgid "Connect with Recapture" +msgstr "" + +#. translators: Tax rate country code +#: includes/admin/settings/register-settings.php:1659 +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:1660 +msgid "Please select a country." +msgstr "" + +#: includes/admin/settings/register-settings.php:1661 +msgid "Please enter a tax rate greater than 0." +msgstr "" + +#: includes/admin/settings/register-settings.php:1662 +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 +#: src/Gateways/Stripe/PaymentMethods/Link.php:58 +msgid "Link" +msgstr "" + +#: includes/admin/thickbox.php:59 +#: includes/template-functions.php:528 +msgid "Button" +msgstr "" + +#: includes/admin/thickbox.php:77 +#: includes/blocks/includes/downloads/downloads.php:136 +#: includes/checkout/template.php:1004 +#: includes/gateways/stripe/includes/payment-methods/buy-now/template.php:212 +#: includes/shortcodes.php:49 +#: includes/template-functions.php:92 +#: src/Admin/Settings/Tabs/Misc.php:129 +msgid "Purchase" +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:34 +msgid "Use these tools to recount / reset store stats." +msgstr "" + +#: includes/admin/tools.php:41 +#: src/Assets/Localization.php:63 +msgid "Please select an option" +msgstr "" + +#: includes/admin/tools.php:43 +msgid "Recount Store Earnings and Sales" +msgstr "" + +#. translators: %s: Singular download label, lowercase +#: includes/admin/tools.php:48 +msgid "Recount Earnings and Sales for a %s" +msgstr "" + +#. translators: %s: Plural download label, lowercase +#: includes/admin/tools.php:55 +msgid "Recount Earnings and Sales for All %s" +msgstr "" + +#: includes/admin/tools.php:62 +msgid "Reset Store" +msgstr "" + +#: includes/admin/tools.php:76 +msgid "Submit" +msgstr "" + +#: includes/admin/tools.php:82 +msgid "Recalculates the total store earnings and sales." +msgstr "" + +#. translators: %s: Singular wownload label, lowercase +#: includes/admin/tools.php:86 +msgid "Recalculates the earnings and sales stats for a specific %s." +msgstr "" + +#. translators: %s: Pural download label, lowercase +#: includes/admin/tools.php:92 +msgid "Recalculates the earnings and sales stats for all %s." +msgstr "" + +#: includes/admin/tools.php:95 +msgid "Recalculates the lifetime value and purchase counts for all customers." +msgstr "" + +#: includes/admin/tools.php:97 +msgid "Deletes all payment records, customers, and related log entries." +msgstr "" + +#: includes/admin/tools.php:127 +#: includes/admin/tools.php:136 +msgid "Clear Incomplete Upgrade Notice" +msgstr "" + +#: includes/admin/tools.php:129 +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:130 +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: API documentation linktag, 2: iOS app link tag, 3: closing link tag +#: includes/admin/tools.php:185 +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:217 +msgid "Enable Beta Versions" +msgstr "" + +#: includes/admin/tools.php:219 +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:235 +msgid "Get updates for pre-release versions of %s" +msgstr "" + +#: includes/admin/tools.php:391 +msgid "Import Orders" +msgstr "" + +#: includes/admin/tools.php:393 +msgid "Import a CSV file of orders." +msgstr "" + +#: includes/admin/tools.php:405 +#: includes/admin/tools.php:724 +msgid "Import CSV" +msgstr "" + +#. translators: %1$s opening anchor tag, %2$s closing anchor tag +#: includes/admin/tools.php:418 +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:429 +msgid "Payment Field" +msgstr "" + +#: includes/admin/tools.php:430 +#: includes/admin/tools.php:749 +msgid "CSV Column" +msgstr "" + +#: includes/admin/tools.php:431 +#: includes/admin/tools.php:750 +msgid "Data Preview" +msgstr "" + +#: includes/admin/tools.php:436 +msgid "Currency Code" +msgstr "" + +#: includes/admin/tools.php:440 +#: includes/admin/tools.php:449 +#: includes/admin/tools.php:459 +#: includes/admin/tools.php:469 +#: includes/admin/tools.php:479 +#: includes/admin/tools.php:489 +#: includes/admin/tools.php:499 +#: includes/admin/tools.php:509 +#: includes/admin/tools.php:519 +#: includes/admin/tools.php:529 +#: includes/admin/tools.php:539 +#: includes/admin/tools.php:549 +#: includes/admin/tools.php:558 +#: includes/admin/tools.php:568 +#: includes/admin/tools.php:578 +#: includes/admin/tools.php:588 +#: includes/admin/tools.php:597 +#: includes/admin/tools.php:606 +#: includes/admin/tools.php:616 +#: includes/admin/tools.php:626 +#: includes/admin/tools.php:636 +#: includes/admin/tools.php:646 +#: includes/admin/tools.php:656 +#: includes/admin/tools.php:665 +#: includes/admin/tools.php:674 +#: includes/admin/tools.php:684 +#: includes/admin/tools.php:694 +#: includes/admin/tools.php:759 +#: includes/admin/tools.php:769 +#: includes/admin/tools.php:779 +#: includes/admin/tools.php:789 +#: includes/admin/tools.php:799 +#: includes/admin/tools.php:809 +#: includes/admin/tools.php:818 +#: includes/admin/tools.php:827 +#: includes/admin/tools.php:836 +#: includes/admin/tools.php:846 +#: includes/admin/tools.php:856 +#: includes/admin/tools.php:865 +#: includes/admin/tools.php:875 +#: includes/admin/tools.php:884 +#: includes/admin/tools.php:894 +#: includes/admin/tools.php:903 +#: includes/admin/tools.php:913 +msgid "- Ignore this field -" +msgstr "" + +#: includes/admin/tools.php:443 +#: includes/admin/tools.php:452 +#: includes/admin/tools.php:462 +#: includes/admin/tools.php:472 +#: includes/admin/tools.php:482 +#: includes/admin/tools.php:492 +#: includes/admin/tools.php:502 +#: includes/admin/tools.php:512 +#: includes/admin/tools.php:522 +#: includes/admin/tools.php:532 +#: includes/admin/tools.php:542 +#: includes/admin/tools.php:552 +#: includes/admin/tools.php:561 +#: includes/admin/tools.php:571 +#: includes/admin/tools.php:581 +#: includes/admin/tools.php:591 +#: includes/admin/tools.php:600 +#: 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:677 +#: includes/admin/tools.php:687 +#: includes/admin/tools.php:697 +#: includes/admin/tools.php:762 +#: includes/admin/tools.php:772 +#: includes/admin/tools.php:782 +#: includes/admin/tools.php:792 +#: includes/admin/tools.php:802 +#: includes/admin/tools.php:812 +#: includes/admin/tools.php:821 +#: includes/admin/tools.php:830 +#: includes/admin/tools.php:839 +#: includes/admin/tools.php:849 +#: includes/admin/tools.php:859 +#: includes/admin/tools.php:868 +#: includes/admin/tools.php:878 +#: includes/admin/tools.php:887 +#: includes/admin/tools.php:897 +#: includes/admin/tools.php:906 +#: includes/admin/tools.php:916 +msgid "- select field to preview data -" +msgstr "" + +#: includes/admin/tools.php:495 +msgid "Discount Code(s)" +msgstr "" + +#: includes/admin/tools.php:525 +msgid "Parent Payment ID" +msgstr "" + +#: includes/admin/tools.php:574 +msgid "Purchased Product(s)" +msgstr "" + +#: includes/admin/tools.php:642 +#: includes/gateways/stripe/includes/template-functions.php:524 +msgid "Address Line 1" +msgstr "" + +#: includes/admin/tools.php:652 +#: includes/blocks/views/checkout/purchase-form/address.php:43 +#: includes/gateways/stripe/includes/template-functions.php:540 +msgid "Address Line 2" +msgstr "" + +#: includes/admin/tools.php:702 +#: includes/admin/tools.php:921 +msgid "Process Import" +msgstr "" + +#: includes/admin/tools.php:710 +msgid "Import Download Products" +msgstr "" + +#: includes/admin/tools.php:712 +msgid "Import a CSV file of products." +msgstr "" + +#. translators: %1$s and %2$s opening and closing anchor tags respectively +#: includes/admin/tools.php:737 +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:748 +msgid "Product Field" +msgstr "" + +#: includes/admin/tools.php:755 +msgid "Product Author" +msgstr "" + +#: includes/admin/tools.php:765 +#: includes/reports/reports-functions.php:336 +msgid "Product Categories" +msgstr "" + +#: includes/admin/tools.php:775 +msgid "Product Creation Date" +msgstr "" + +#: includes/admin/tools.php:785 +msgid "Product Description" +msgstr "" + +#: includes/admin/tools.php:795 +msgid "Product Excerpt" +msgstr "" + +#: includes/admin/tools.php:805 +#: src/Admin/Onboarding/Steps/Products.php:57 +msgid "Product Image" +msgstr "" + +#: includes/admin/tools.php:815 +msgid "Product Notes" +msgstr "" + +#: includes/admin/tools.php:824 +msgid "Product Price(s)" +msgstr "" + +#: includes/admin/tools.php:833 +msgid "Product SKU" +msgstr "" + +#: includes/admin/tools.php:842 +msgid "Product Slug" +msgstr "" + +#: includes/admin/tools.php:852 +msgid "Product Status" +msgstr "" + +#: includes/admin/tools.php:862 +msgid "Product Tags" +msgstr "" + +#: includes/admin/tools.php:871 +msgid "Product Title" +msgstr "" + +#: includes/admin/tools.php:881 +msgid "Download Files" +msgstr "" + +#: includes/admin/tools.php:900 +msgid "Sale Count" +msgstr "" + +#: includes/admin/tools.php:929 +msgid "Export Settings" +msgstr "" + +#: includes/admin/tools.php:931 +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:937 +msgid "To export shop data (purchases, customers, etc), visit the Reports page." +msgstr "" + +#: includes/admin/tools.php:962 +msgid "Import Settings" +msgstr "" + +#: includes/admin/tools.php:964 +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:973 +msgid "Import" +msgstr "" + +#: includes/admin/tools.php:1051 +msgid "Please upload a valid .json file" +msgstr "" + +#: includes/admin/tools.php:1057 +msgid "Please upload a file to import" +msgstr "" + +#: includes/admin/tools.php:1124 +msgid "No File" +msgstr "" + +#: includes/admin/tools.php:1127 +msgid "Log is Empty" +msgstr "" + +#: includes/admin/tools.php:1131 +#: src/Admin/Tools/Screen.php:74 +msgid "Debug Log" +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:1139 +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:1155 +msgid "Download Debug Log File" +msgstr "" + +#: includes/admin/tools.php:1160 +msgid "Clear Log" +msgstr "" + +#: includes/admin/tools.php:1169 +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:133 +msgid "Customer stats successfully recounted." +msgstr "" + +#. translators: %s: download title +#: includes/admin/tools/class-edd-tools-recount-download-stats.php:138 +msgid "Earnings and sales stats successfully recounted for %s." +msgstr "" + +#: includes/admin/tools/class-edd-tools-recount-single-customer-stats.php:123 +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:152 +msgid "Your store has been successfully reset." +msgstr "" + +#: includes/admin/tools/logs.php:175 +msgid "Payment Errors" +msgstr "" + +#: includes/admin/tools/logs.php:176 +msgid "API Requests" +msgstr "" + +#: includes/admin/upgrades/classes/class-file-download-log-migration.php:133 +#: src/Admin/Upgrades/v3/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:45 +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:88 +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:97 +msgid "Easy Digital Downloads needs to upgrade the database. %1$sLearn more about this upgrade%2$s." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:106 +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:112 +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:122 +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:132 +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:141 +msgid "For large sites, this is the recommended method of upgrading." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:154 +msgid "Begin the upgrade" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:165 +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:172 +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:180 +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:350 +msgid "I have secured a backup of my website data." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:355 +msgid "Upgrade Easy Digital Downloads" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:382 +#: src/Admin/Assets/Localization.php:41 +msgid "Migration complete" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:387 +msgid "Migration pending" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:406 +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:430 +#: includes/admin/upgrades/upgrade-functions.php:502 +#: includes/upgrades/functions.php:176 +msgid "Remove Legacy Data" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:433 +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 "" + +#. translators: 1: Opening anchor tag; do not translate. 2: Closing anchor tag; do not translate. +#: includes/admin/upgrades/upgrade-functions.php:440 +msgid "You can complete this step later by navigating to %1$sDownloads » Tools%2$s." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:451 +msgid "I have confirmed my store is operational and I have a backup of my website data." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:459 +msgid "Permanently Remove Legacy Data" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:470 +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:118 +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 "" + +#. translators: 1: Step number, %2$d: Total steps +#: includes/admin/upgrades/upgrades.php:99 +msgid "Step %1$d of approximately %2$d running" +msgstr "" + +#. translators: %s: Upgrade step name/key +#: includes/admin/upgrades/v3/upgrade-actions.php:45 +msgid "\"%s\" is not a valid 3.0 upgrade." +msgstr "" + +#: includes/admin/upgrades/v3/upgrade-actions.php:63 +msgid "Error loading migration class." +msgstr "" + +#: includes/admin/views/email-editor/content.php:32 +#: src/Admin/Onboarding/Steps/ConfigureEmails.php:103 +msgid "Message" +msgstr "" + +#: includes/admin/views/email-editor/content.php:41 +msgid "The content for this email is not editable." +msgstr "" + +#: includes/admin/views/email-editor/header.php:17 +msgid "Email Updated" +msgstr "" + +#: includes/admin/views/email-editor/header.php:20 +msgid "Email Not Updated" +msgstr "" + +#: includes/admin/views/email-editor/header.php:57 +msgid "Saving Changes" +msgstr "" + +#: includes/admin/views/email-editor/heading.php:18 +msgid "Heading" +msgstr "" + +#. translators: %s: Date and time of the scheduled event. +#: includes/admin/views/email-editor/legacy.php:35 +msgid "Success! This email has been converted to the new email management system. We will keep a backup of the old settings which are no longer used, until %s, after which we will remove them to improve site performance." +msgstr "" + +#: includes/admin/views/email-editor/recipient.php:20 +#: includes/admin/views/email-editor/recipient.php:62 +msgid "Send To" +msgstr "" + +#: includes/admin/views/email-editor/recipient.php:51 +#: src/Admin/Settings/Tabs/Emails.php:76 +msgid "Admin Email Recipients" +msgstr "" + +#: includes/admin/views/email-editor/recipient.php:55 +#: includes/admin/views/email-editor/recipient.php:131 +#: src/Admin/Settings/Tabs/Emails.php:179 +#: src/Admin/Settings/Tabs/Emails.php:185 +msgid "Custom Recipients" +msgstr "" + +#: includes/admin/views/email-editor/recipient.php:100 +msgid "Recipients" +msgstr "" + +#. translators: 1: opening anchor tag; 2: closing anchor tag +#: includes/admin/views/email-editor/recipient.php:114 +msgid "Update the admin email recipients in the %1$semail settings%2$s." +msgstr "" + +#: includes/admin/views/email-editor/recipient.php:135 +msgid "Enter one email per line." +msgstr "" + +#: includes/admin/views/email-editor/sender.php:15 +msgid "From" +msgstr "" + +#: includes/admin/views/email-editor/status.php:23 +#: includes/class-edd-cli.php:68 +#: includes/class-edd-cli.php:78 +#: includes/class-edd-cli.php:89 +#: includes/class-edd-cli.php:98 +#: includes/class-edd-cli.php:159 +msgid "Enabled" +msgstr "" + +#: includes/admin/views/email-editor/subject.php:15 +#: includes/admin/views/email-editor/subject.php:26 +#: src/Admin/Emails/ListTable.php:79 +#: src/Admin/Emails/LogsTable.php:71 +msgid "Subject" +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 "" + +#. translators: %s: Download singular label +#: includes/admin/views/tmpl-order-actions.php:41 +#: includes/admin/views/tmpl-order-form-add-order-item.php:254 +msgid "Add %s" +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:47 +msgid "Order items cannot be modified." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:50 +msgid "Issue a refund to adjust the net total for this order." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:59 +msgid "Amazon orders must be refunded at the gateway." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:64 +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:74 +msgid "The refund window for this Order has passed." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:89 +msgid "Initialize Refund" +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:94 +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:1336 +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:1074 +#: includes/blocks/views/checkout/discount.php:16 +#: includes/blocks/views/orders/discounts.php:7 +#: includes/checkout/template.php:753 +#: includes/discount-functions.php:1300 +#: templates/shortcode-receipt.php:87 +msgid "Discount" +msgid_plural "Discounts" +msgstr[0] "" +msgstr[1] "" + +#. translators: %s: Adjustment type (discount, fee, etc) +#: includes/admin/views/tmpl-order-adjustment.php:30 +#: includes/admin/views/tmpl-order-item.php:25 +msgid "Remove %s" +msgstr "" + +#: includes/admin/views/tmpl-order-adjustment.php:46 +#: 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 +#: src/Emails/Tags/Render.php:242 +#: templates/shortcode-receipt.php:116 +#: templates/shortcode-receipt.php:120 +msgid "Fee" +msgid_plural "Fees" +msgstr[0] "" +msgstr[1] "" + +#. translators: %s: Download label singular +#: includes/admin/views/tmpl-order-copy-download-link.php:20 +msgid "%s Links" +msgstr "" + +#: includes/admin/views/tmpl-order-copy-download-link.php:27 +msgid "No file links available" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:46 +#: includes/ajax-functions.php:1108 +#: 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 "" + +#. translators: %s: Tax rate as a percentage +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:121 +#: 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:141 +#: 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:142 +#: 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:146 +#: 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 "" + +#. translators: %s: Download label singular +#: 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 "" + +#. translators: %s: Tax rate as a percentage +#: includes/admin/views/tmpl-order-form-add-order-item.php:201 +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 "" + +#. translators: %s: Tax rate +#: includes/admin/views/tmpl-order-tax.php:47 +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:59 +msgid "Update Amounts" +msgstr "" + +#: includes/admin/views/tmpl-order-tax.php:64 +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:23 +#: src/HTML/Elements.php:455 +msgid "All Countries" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-add.php:29 +#: src/Admin/Settings/Tabs/General.php:101 +msgid "Select a country" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-add.php:39 +msgid "Apply to whole country" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-add.php:46 +#: 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:51 +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:138 +#: includes/cart/actions.php:109 +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:196 +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:490 +msgid "Untitled" +msgstr "" + +#: includes/ajax-functions.php:507 +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:561 +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:675 +msgid "No results found" +msgstr "" + +#: includes/ajax-functions.php:717 +msgid "No categories found" +msgstr "" + +#: includes/ajax-functions.php:736 +#: includes/ajax-functions.php:876 +#: src/HTML/Elements.php:208 +msgid "No users found" +msgstr "" + +#: includes/ajax-functions.php:807 +msgid "All Prices" +msgstr "" + +#: includes/ajax-functions.php:1328 +#: includes/ajax-functions.php:1337 +msgid "Unable to verify action. Please refresh the page and try again." +msgstr "" + +#: includes/ajax-functions.php:1378 +msgid "Unable to find download. Please refresh the page and try again." +msgstr "" + +#. translators: %s: product ID. +#: includes/api/class-edd-api-v2.php:177 +#: includes/api/class-edd-api.php:1125 +#: includes/api/class-edd-api.php:1335 +#: includes/api/class-edd-api.php:1457 +msgid "Product %s not found!" +msgstr "" + +#: includes/api/class-edd-api-v2.php:360 +msgid "Customer not found!" +msgstr "" + +#: includes/api/class-edd-api-v2.php:365 +#: includes/api/class-edd-api.php:1074 +msgid "No customers found!" +msgstr "" + +#: includes/api/class-edd-api.php:462 +msgid "You must specify both a token and API key!" +msgstr "" + +#: includes/api/class-edd-api.php:479 +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:685 +msgid "Invalid query!" +msgstr "" + +#. translators: %s: customer ID. +#: includes/api/class-edd-api.php:1068 +msgid "Customer %s not found!" +msgstr "" + +#: includes/api/class-edd-api.php:1233 +#: includes/api/class-edd-api.php:1350 +msgid "The end date must be later than the start date!" +msgstr "" + +#: includes/api/class-edd-api.php:1238 +#: includes/api/class-edd-api.php:1355 +msgid "Invalid or no date range specified!" +msgstr "" + +#: includes/api/class-edd-api.php:1625 +msgid "No discounts found!" +msgstr "" + +#. translators: %s: discount ID. +#: includes/api/class-edd-api.php:1671 +msgid "Discount %s not found!" +msgstr "" + +#: includes/api/class-edd-api.php:1717 +msgid "No download logs found!" +msgstr "" + +#: includes/api/class-edd-api.php:1983 +msgid "User ID Required" +msgstr "" + +#. translators: %s: action being performed. +#: includes/api/class-edd-api.php:2002 +#: includes/api/class-edd-api.php:2012 +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:70 +msgid "Confirmation" +msgstr "" + +#: includes/blocks/edd-blocks.php:71 +#: includes/install.php:291 +msgid "Thank you for your purchase!" +msgstr "" + +#: includes/blocks/edd-blocks.php:74 +#: includes/emails/template.php:130 +#: src/Emails/Tags/Registry.php:182 +msgid "Receipt" +msgstr "" + +#: includes/blocks/edd-blocks.php:78 +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:131 +#: src/Admin/Settings/Tabs/General.php:140 +#: src/Admin/Settings/Tabs/General.php:149 +#: src/Admin/Settings/Tabs/General.php:158 +#: src/Admin/Settings/Tabs/General.php:171 +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/cart/template.php:130 +#: includes/gateways/stripe/includes/compat.php:118 +#: includes/process-purchase.php:30 +msgid "Your cart is empty." +msgstr "" + +#: includes/blocks/includes/checkout/forms.php:197 +#: includes/blocks/includes/checkout/forms.php:211 +msgid "Unable to load form." +msgstr "" + +#: includes/blocks/includes/checkout/forms.php:223 +#: includes/blocks/views/checkout/purchase-form/login.php:29 +#: includes/checkout/template.php:497 +#: includes/checkout/template.php:614 +msgid "Log in" +msgstr "" + +#: includes/blocks/includes/checkout/forms.php:227 +msgid "Register for a new account" +msgstr "" + +#: includes/blocks/includes/checkout/forms.php:231 +msgid "Checkout as a guest" +msgstr "" + +#: includes/blocks/includes/checkout/functions.php:44 +msgid "item" +msgid_plural "items" +msgstr[0] "" +msgstr[1] "" + +#. translators: the plurals downloads name. +#: includes/blocks/includes/downloads/downloads.php:91 +msgctxt "download post type name" +msgid "No %s found." +msgstr "" + +#: includes/blocks/includes/forms/forms.php:70 +#: includes/users/lost-password.php:232 +msgid "Your password reset link appears to be invalid. Please request a new link below." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:112 +msgid "reCAPTCHA validation missing." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:148 +msgid "There was an error validating the reCAPTCHA. Please try again." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:165 +msgid "Unexpected reCAPTCHA error. Please try again." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:172 +msgid "reCAPTCHA verification failed. Please contact a site administrator." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:179 +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:146 +msgid "View Order Details" +msgstr "" + +#: includes/blocks/includes/orders/orders.php:115 +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:118 +msgid "Your purchase session could not be retrieved." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:168 +#: includes/shortcodes.php:671 +msgid "Sorry, trouble retrieving order receipt." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:179 +msgid "To view a sample receipt, you need to have at least one order in your store." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:223 +msgid "Sorry, you do not have permission to view this receipt." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:232 +msgid "Please confirm your email address to access your downloads." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:242 +msgid "Please log in to view your order." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:274 +msgid "Please log in to access your downloads." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:290 +#: includes/blocks/includes/orders/orders.php:302 +msgid "Your email address could not be verified." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:329 +#: includes/blocks/views/orders/receipt-files.php:70 +#: includes/emails/tags.php:270 +#: 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/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:758 +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:148 +#: includes/gateways/stripe/includes/template-functions.php:170 +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:151 +#: includes/gateways/stripe/includes/template-functions.php:174 +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:216 +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:635 +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:959 +#: includes/gateways/stripe/includes/scripts.php:87 +#: src/Admin/Pointers.php:41 +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:14 +#: templates/shortcode-login.php:15 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/register/index.js:1 +#: includes/blocks/src/login/edit.js:42 +#: includes/blocks/src/register/edit.js:42 +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:36 +#: includes/checkout/template.php:522 +#: includes/checkout/template.php:529 +#: includes/checkout/template.php:602 +#: templates/shortcode-login.php:19 +#: templates/shortcode-register.php:30 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/register/index.js:1 +#: includes/blocks/src/login/edit.js:48 +#: includes/blocks/src/register/edit.js:60 +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:58 +#: templates/shortcode-register.php:35 +#: includes/blocks/build/register/index.js:1 +#: includes/blocks/src/register/edit.js:69 +msgid "Confirm Password" +msgstr "" + +#: includes/blocks/views/downloads/content.php:18 +#: includes/template-functions.php:152 +msgid "Free" +msgstr "" + +#: includes/blocks/views/forms/login.php:33 +#: templates/shortcode-login.php:23 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/src/login/edit.js:57 +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 +#: includes/blocks/src/login/edit.js:67 +#: includes/blocks/src/login/edit.js:68 +#: includes/blocks/src/register/edit.js:82 +#: includes/blocks/src/register/edit.js:83 +msgid "Log In" +msgstr "" + +#: includes/blocks/views/forms/login.php:44 +#: includes/payments/actions.php:343 +#: templates/shortcode-login.php:33 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/src/login/edit.js:74 +msgid "Lost Password?" +msgstr "" + +#: includes/blocks/views/forms/lost-password.php:9 +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:15 +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:43 +#: includes/blocks/views/forms/reset-password.php:17 +msgid "Hide password" +msgstr "" + +#: includes/blocks/views/forms/registration.php:46 +#: includes/blocks/views/forms/reset-password.php:20 +msgid "Strength indicator" +msgstr "" + +#: includes/blocks/views/forms/registration.php:52 +#: 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:581 +#: 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:15 +#: templates/shortcode-receipt.php:42 +msgctxt "heading" +msgid "Order" +msgstr "" + +#: includes/blocks/views/orders/totals.php:20 +#: templates/shortcode-receipt.php:50 +msgid "Order Status" +msgstr "" + +#: includes/blocks/views/orders/totals.php:26 +#: 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 "" + +#. translators: %s: tax rate formatted as a percentage +#: includes/cart/class-edd-cart.php:894 +msgid "includes %s tax" +msgstr "" + +#. translators: %s: tax rate formatted as a percentage +#: includes/cart/class-edd-cart.php:897 +msgid "excludes %s tax" +msgstr "" + +#: includes/cart/class-edd-cart.php:1403 +#: includes/cart/class-edd-cart.php:1463 +#: includes/error-tracking.php:84 +#: templates/shortcode-profile-editor.php:28 +msgid "Success" +msgstr "" + +#: includes/cart/class-edd-cart.php:1404 +msgid "Cart saved successfully. You can restore your cart using this URL:" +msgstr "" + +#: includes/cart/class-edd-cart.php:1438 +#: includes/cart/class-edd-cart.php:1452 +msgid "Cart restoration failed. Invalid token." +msgstr "" + +#: includes/cart/class-edd-cart.php:1446 +#: includes/cart/class-edd-cart.php:1455 +msgid "The cart cannot be restored. Invalid token." +msgstr "" + +#: includes/cart/class-edd-cart.php:1463 +msgid "Cart restored successfully." +msgstr "" + +#: includes/cart/template.php:203 +msgid "Restore Previous Cart" +msgstr "" + +#: includes/cart/template.php:205 +msgid "Save Cart" +msgstr "" + +#: includes/cart/template.php:229 +msgid "Restore Previous Cart." +msgstr "" + +#: includes/cart/template.php:247 +msgid "Update Cart" +msgstr "" + +#. Translators: %s: name of the download that was added to the cart. +#: includes/cart/template.php:310 +msgid "You have successfully added %s to your shopping cart." +msgstr "" + +#: includes/cart/template.php:311 +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:173 +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:883 +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:849 +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:516 +msgid "The username you will use to log into your account." +msgstr "" + +#: includes/checkout/template.php:528 +msgid "The password used to access your account." +msgstr "" + +#: includes/checkout/template.php:534 +msgid "Password again" +msgstr "" + +#: includes/checkout/template.php:540 +msgid "Confirm your password." +msgstr "" + +#: includes/checkout/template.php:541 +msgid "Confirm password" +msgstr "" + +#: includes/checkout/template.php:579 +msgid "Need to create an account?" +msgstr "" + +#: includes/checkout/template.php:581 +msgid "or checkout as a guest" +msgstr "" + +#: includes/checkout/template.php:591 +msgid "Username or email" +msgstr "" + +#: includes/checkout/template.php:597 +msgid "Your username or email address" +msgstr "" + +#: includes/checkout/template.php:608 +msgid "Your password" +msgstr "" + +#: includes/checkout/template.php:646 +msgid "Select payment method" +msgstr "" + +#: includes/checkout/template.php:749 +msgid "Have a discount code?" +msgstr "" + +#: includes/checkout/template.php:749 +msgctxt "Entering a discount code" +msgid "Click to enter it" +msgstr "" + +#: includes/checkout/template.php:755 +msgid "Enter a discount code if you have one." +msgstr "" + +#: includes/checkout/template.php:757 +#: includes/process-purchase.php:316 +#: src/Assets/Localization.php:98 +msgid "Enter discount" +msgstr "" + +#: includes/checkout/template.php:785 +msgid "Agree to Terms?" +msgstr "" + +#: includes/checkout/template.php:806 +msgid "Show Terms" +msgstr "" + +#: includes/checkout/template.php:807 +msgid "Hide Terms" +msgstr "" + +#: includes/checkout/template.php:856 +msgid "Agree to Privacy Policy?" +msgstr "" + +#: includes/checkout/template.php:878 +msgid "Show Privacy Policy" +msgstr "" + +#: includes/checkout/template.php:880 +msgid "Hide Privacy Policy" +msgstr "" + +#: includes/checkout/template.php:914 +msgid "Purchase Total:" +msgstr "" + +#: includes/checkout/template.php:938 +msgid "Go back" +msgstr "" + +#: includes/checkout/template.php:1009 +#: src/Admin/Settings/Tabs/Misc.php:136 +msgid "Free Download" +msgstr "" + +#: includes/class-easy-digital-downloads.php:274 +#: includes/class-easy-digital-downloads.php:286 +msgid "Method Not Allowed." +msgstr "" + +#. translators: %s: W3 Total Cache settings URL +#: includes/class-edd-cache-helper.php:114 +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 "" + +#. translators: %s: EDD version +#: includes/class-edd-cli.php:61 +msgid "You are running EDD version: %s" +msgstr "" + +#. translators: %s: The status of test mode, either Enabled or Disabled +#: includes/class-edd-cli.php:66 +msgid "Test mode is: %s" +msgstr "" + +#. translators: %s: The status of AJAX, either Enabled or Disabled +#: includes/class-edd-cli.php:76 +msgid "AJAX is: %s" +msgstr "" + +#. translators: %s: The status of guest checkouts, either Enabled or Disabled +#: includes/class-edd-cli.php:86 +msgid "Guest checkouts are: %s" +msgstr "" + +#. translators: %s: The status of file downloads via symlink setting, either Enabled or Disabled +#: includes/class-edd-cli.php:96 +msgid "Symlinks are: %s" +msgstr "" + +#. translators: %s: The status of the checkout page, either Valid or Invalid +#: includes/class-edd-cli.php:107 +msgid "Checkout page is: %s" +msgstr "" + +#: includes/class-edd-cli.php:109 +msgid "Valid" +msgstr "" + +#: includes/class-edd-cli.php:110 +msgid "Invalid" +msgstr "" + +#. translators: %s: The URL of the checkout page +#: includes/class-edd-cli.php:116 +msgid "Checkout URL is: %s" +msgstr "" + +#: includes/class-edd-cli.php:119 +#: includes/class-edd-cli.php:129 +#: includes/class-edd-cli.php:139 +msgid "Undefined" +msgstr "" + +#. translators: %s: The URL of the success page +#: includes/class-edd-cli.php:126 +msgid "Success URL is: %s" +msgstr "" + +#. translators: %s: The URL of the failure page +#: includes/class-edd-cli.php:136 +msgid "Failure URL is: %s" +msgstr "" + +#. translators: %s: The download slug used in the WordPress Permalinks, defaults to /downloads +#: includes/class-edd-cli.php:146 +msgid "Downloads slug is: %s" +msgstr "" + +#. translators: %s: The status of the taxes enabled setting, either Enabled or Disabled +#: includes/class-edd-cli.php:157 +msgid "Taxes are: %s" +msgstr "" + +#. translators: %s: The default tax rate formatted as a percentage +#: includes/class-edd-cli.php:165 +msgid "Tax rate is: %s" +msgstr "" + +#. translators: 1: The country code, 2: The state code, 3: The tax rate formatted as a percentage +#: includes/class-edd-cli.php:171 +msgid "Country: %1$s, State: %2$s, Rate: %3$s" +msgstr "" + +#. translators: %s: The earnings formatted for the currency +#: includes/class-edd-cli.php:215 +msgid "Earnings: %s" +msgstr "" + +#. translators: %s: The sales count +#: includes/class-edd-cli.php:217 +msgid "Sales: %s" +msgstr "" + +#: includes/class-edd-cli.php:242 +msgid "No Downloads found" +msgstr "" + +#. translators: %d: The product ID (post ID) +#: includes/class-edd-cli.php:282 +msgctxt "The Download/Product ID" +msgid "ID: %d" +msgstr "" + +#. translators: %s: The status of the product +#: includes/class-edd-cli.php:284 +msgctxt "The Download/Product Status" +msgid "Status: %s" +msgstr "" + +#. translators: %s: The date the product was posted +#: includes/class-edd-cli.php:286 +msgid "Posted: %s" +msgstr "" + +#. translators: %s: The product categories +#: includes/class-edd-cli.php:288 +msgid "Categories: %s" +msgstr "" + +#. translators: %s: The product tags +#: includes/class-edd-cli.php:290 +msgid "Tags: %s" +msgstr "" + +#. translators: %s: The product pricing +#: includes/class-edd-cli.php:292 +msgid "Pricing: %s" +msgstr "" + +#. translators: %s: The product sales count +#: includes/class-edd-cli.php:294 +msgctxt "The sales count for the product" +msgid "Sales: %s" +msgstr "" + +#. translators: %s: The product earnings formatted for the currency +#: includes/class-edd-cli.php:296 +msgctxt "The product earnings" +msgid "Earnings: %s" +msgstr "" + +#. translators: %s: The product page slug +#: includes/class-edd-cli.php:299 +msgid "Slug: %s" +msgstr "" + +#. translators: %s: The product link +#: includes/class-edd-cli.php:301 +msgid "Permalink: %s" +msgstr "" + +#: includes/class-edd-cli.php:305 +msgid "Download Files:" +msgstr "" + +#. translators: 1: The file name, 2: The file path +#: includes/class-edd-cli.php:309 +msgid "File: %1$s (%2$s)" +msgstr "" + +#. translators: %s: The price assignment condition +#: includes/class-edd-cli.php:313 +msgid "Price Assignment: %s" +msgstr "" + +#. translators: %d: The customer ID +#: includes/class-edd-cli.php:377 +msgid "Customer %d created successfully" +msgstr "" + +#: includes/class-edd-cli.php:379 +msgid "Failed to create customer" +msgstr "" + +#. translators: 1: The number of customers created, %2$d: The time it took to create the customers +#: includes/class-edd-cli.php:388 +msgid "%1$d customers created in %2$d seconds" +msgstr "" + +#: includes/class-edd-cli.php:416 +#: src/HTML/Elements.php:139 +msgid "No customers found" +msgstr "" + +#. translators: %d: The customer's user ID +#: includes/class-edd-cli.php:424 +msgid "Customer User ID: %s" +msgstr "" + +#. translators: %s: The customer's username +#: includes/class-edd-cli.php:426 +#: includes/deprecated-functions.php:2497 +#: includes/deprecated-functions.php:2512 +msgid "Username: %s" +msgstr "" + +#. translators: %s: The customer's display name +#: includes/class-edd-cli.php:428 +msgid "Display Name: %s" +msgstr "" + +#. translators: %s: The customer's first name +#: includes/class-edd-cli.php:432 +msgid "First Name: %s" +msgstr "" + +#. translators: %s: The customer's surname +#: includes/class-edd-cli.php:437 +msgid "Last Name: %s" +msgstr "" + +#. translators: %s: The customer's email address +#: includes/class-edd-cli.php:441 +#: includes/class-edd-cli.php:489 +msgid "Email: %s" +msgstr "" + +#. translators: %s: The customer's total purchases +#: includes/class-edd-cli.php:445 +msgid "Purchases: %s" +msgstr "" + +#. translators: %s: The customer's total spent +#: includes/class-edd-cli.php:447 +msgid "Total Spent: %s" +msgstr "" + +#. translators: %s: The customer's total downloads +#: includes/class-edd-cli.php:449 +msgid "Total Downloads: %s" +msgstr "" + +#: includes/class-edd-cli.php:479 +msgid "No sales found" +msgstr "" + +#. translators: %s: The purchase key +#: includes/class-edd-cli.php:487 +msgid "Purchase Key: %s" +msgstr "" + +#. translators: %s: The purchase date +#: includes/class-edd-cli.php:491 +msgid "Date: %s" +msgstr "" + +#. translators: %s: The purchase subtotal +#: includes/class-edd-cli.php:493 +msgid "Subtotal: %s" +msgstr "" + +#. translators: %s: The purchase tax +#: includes/class-edd-cli.php:495 +msgid "Tax: %s" +msgstr "" + +#: includes/class-edd-cli.php:498 +msgid "Fees:" +msgstr "" + +#. translators: 1: The fee amount, 2: The currency +#: includes/class-edd-cli.php:502 +msgid " Fee: %1$s - %2$s" +msgstr "" + +#. translators: %s: The purchase total +#: includes/class-edd-cli.php:507 +msgid "Total: %s" +msgstr "" + +#. translators: %s: The payment gateway used +#: includes/class-edd-cli.php:510 +msgid "Gateway: %s" +msgstr "" + +#: includes/class-edd-cli.php:513 +msgid "Products:" +msgstr "" + +#. translators: 1: The product name, 2: The product price +#: includes/class-edd-cli.php:518 +msgid " Product: %1$s - %2$s" +msgstr "" + +#: includes/class-edd-cli.php:549 +#: includes/shortcodes.php:261 +#: src/HTML/Elements.php:309 +msgid "No discounts found" +msgstr "" + +#. translators: %s: The name of the discount +#: includes/class-edd-cli.php:557 +msgid "Name: %s" +msgstr "" + +#. translators: %s: The code of the discount +#: includes/class-edd-cli.php:559 +msgid "Code: %s" +msgstr "" + +#. translators: %s: The amount of the discount +#: includes/class-edd-cli.php:568 +msgid "Amount: %s" +msgstr "" + +#. translators: %s: The number of uses of the discount +#: includes/class-edd-cli.php:570 +msgid "Uses: %s" +msgstr "" + +#. translators: %s: The maximum number of uses of the discount +#: includes/class-edd-cli.php:572 +msgid "Max Uses: %s" +msgstr "" + +#. translators: %s: The start date of the discount +#: includes/class-edd-cli.php:574 +msgid "Start Date: %s" +msgstr "" + +#. translators: %s: The start date of the discount +#: includes/class-edd-cli.php:574 +msgid "No Start Date" +msgstr "" + +#. translators: %s: The expiration date of the discount +#: includes/class-edd-cli.php:576 +msgid "Expiration Date: %s" +msgstr "" + +#. translators: %s: The expiration date of the discount +#: includes/class-edd-cli.php:576 +msgid "No Expiration" +msgstr "" + +#. translators: %s: The status of the discount +#: includes/class-edd-cli.php:578 +msgctxt "The status of the discount code" +msgid "Status: %s" +msgstr "" + +#: includes/class-edd-cli.php:583 +msgid "Product Requirements:" +msgstr "" + +#. translators: %s: The ID of the product required for this discount +#: includes/class-edd-cli.php:587 +msgid " Product: %s" +msgstr "" + +#. translators: %s: The type of discount if it is able to be used on all products. +#: includes/class-edd-cli.php:594 +msgid "Global Discount: %s" +msgstr "" + +#. translators: %s: The type of discount if it is a single use discount. +#: includes/class-edd-cli.php:596 +msgid "Single Use: %s" +msgstr "" + +#: includes/class-edd-cli.php:623 +#: includes/class-edd-cli.php:2245 +msgid "No action specified, did you mean" +msgstr "" + +#: includes/class-edd-cli.php:625 +#: includes/class-edd-cli.php:2247 +msgid "Invalid action specified, did you mean" +msgstr "" + +#. translators: %s: The status of the payment +#: includes/class-edd-cli.php:683 +msgid "Invalid status '%s', defaulting to 'complete'" +msgstr "" + +#: includes/class-edd-cli.php:720 +msgid "Specified ID is not a product" +msgstr "" + +#. translators: %s: The number of orders created +#: includes/class-edd-cli.php:855 +msgid "Created %s orders" +msgstr "" + +#. translators: %s: The number of discounts created +#: includes/class-edd-cli.php:960 +msgid "Created %s discounts" +msgstr "" + +#: includes/class-edd-cli.php:1048 +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:1054 +msgid "The discounts custom database migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1076 +msgid "Migration complete: Discounts" +msgstr "" + +#: includes/class-edd-cli.php:1079 +#: includes/class-edd-cli.php:1199 +#: includes/class-edd-cli.php:1707 +#: includes/class-edd-cli.php:1959 +msgid "Old Records: " +msgstr "" + +#: includes/class-edd-cli.php:1080 +#: includes/class-edd-cli.php:1200 +#: includes/class-edd-cli.php:1708 +#: includes/class-edd-cli.php:1960 +msgid "New Records: " +msgstr "" + +#: includes/class-edd-cli.php:1086 +msgid "No discount records found." +msgstr "" + +#: includes/class-edd-cli.php:1116 +msgid "The logs custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1119 +msgid "Preparing to migrate logs (this can take several minutes)." +msgstr "" + +#: includes/class-edd-cli.php:1122 +msgid "Migrating Logs" +msgstr "" + +#: includes/class-edd-cli.php:1194 +msgid "No log records found." +msgstr "" + +#: includes/class-edd-cli.php:1197 +msgid "Migration complete: Logs" +msgstr "" + +#: includes/class-edd-cli.php:1230 +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:1233 +msgid "Preparing to migrate order notes." +msgstr "" + +#: includes/class-edd-cli.php:1235 +msgid "Migrating Order Notes" +msgstr "" + +#: includes/class-edd-cli.php:1299 +msgid "No order notes found." +msgstr "" + +#: includes/class-edd-cli.php:1301 +msgid "Migration complete: Order Notes" +msgstr "" + +#: includes/class-edd-cli.php:1303 +msgid "Old order notes: " +msgstr "" + +#: includes/class-edd-cli.php:1304 +msgid "New order notes: " +msgstr "" + +#: includes/class-edd-cli.php:1334 +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:1336 +msgid "Preparing to migrate customer notes." +msgstr "" + +#: includes/class-edd-cli.php:1338 +msgid "Migrating Customer Notes" +msgstr "" + +#: includes/class-edd-cli.php:1397 +msgid "No customer notes found." +msgstr "" + +#: includes/class-edd-cli.php:1399 +msgid "Migration complete: Customer Notes" +msgstr "" + +#: includes/class-edd-cli.php:1401 +msgid "Old customer notes: " +msgstr "" + +#: includes/class-edd-cli.php:1402 +msgid "New customer notes: " +msgstr "" + +#: includes/class-edd-cli.php:1436 +msgid "Preparing to migrate additional customer data." +msgstr "" + +#: includes/class-edd-cli.php:1461 +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:1463 +msgid "Preparing to migrate customer address data." +msgstr "" + +#: includes/class-edd-cli.php:1464 +msgid "Migrating Customer Addresses" +msgstr "" + +#: includes/class-edd-cli.php:1535 +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:1537 +msgid "Preparing to migrate customer email addresses (this can take several minutes)." +msgstr "" + +#: includes/class-edd-cli.php:1538 +msgid "Migrating Customer Email Addresses" +msgstr "" + +#: includes/class-edd-cli.php:1647 +msgid "Migration complete: Customer Email Addresses" +msgstr "" + +#: includes/class-edd-cli.php:1675 +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:1678 +msgid "Checking for default tax rate" +msgstr "" + +#: includes/class-edd-cli.php:1681 +msgid "Migrating default tax rate" +msgstr "" + +#: includes/class-edd-cli.php:1705 +msgid "Migration complete: Tax Rates" +msgstr "" + +#: includes/class-edd-cli.php:1745 +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:1751 +msgid "The payments custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1754 +msgid "Preparing to migrate payments." +msgstr "" + +#: includes/class-edd-cli.php:1757 +msgid "Migrating Payments" +msgstr "" + +#: includes/class-edd-cli.php:1784 +msgid "An EDD Payment could not be found for that ID." +msgstr "" + +#: includes/class-edd-cli.php:1787 +msgid "The payment ID must be an integer from the post_id column." +msgstr "" + +#: includes/class-edd-cli.php:1798 +msgid "The starting ID must be an integer from the post_id column." +msgstr "" + +#: includes/class-edd-cli.php:1808 +msgid "The ending ID must be an integer from the post_id column." +msgstr "" + +#: includes/class-edd-cli.php:1817 +#: includes/class-edd-cli.php:2386 +msgid "No conflicting orders were found." +msgstr "" + +#: includes/class-edd-cli.php:1833 +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:1875 +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:1885 +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: %d: The order ID. +#: includes/class-edd-cli.php:1891 +msgid "Deleting order %d." +msgstr "" + +#. translators: %d: The payment ID. +#: includes/class-edd-cli.php:1922 +msgid "Migration failed for payment %d." +msgstr "" + +#: includes/class-edd-cli.php:1945 +msgid "No payment records found." +msgstr "" + +#: includes/class-edd-cli.php:1952 +msgid "Partial order migration complete. Orders Processed: " +msgstr "" + +#: includes/class-edd-cli.php:1953 +msgid "To recalculate all download sales and earnings, run `wp edd recalculate_download_sales_earnings`." +msgstr "" + +#: includes/class-edd-cli.php:1954 +msgid "To recalculate all customer sales and earnings, run `wp edd recalculate_customer_values`." +msgstr "" + +#: includes/class-edd-cli.php:1956 +msgid "Migration complete: Orders" +msgstr "" + +#: includes/class-edd-cli.php:1963 +msgid "Refund Records Created: " +msgstr "" + +#: includes/class-edd-cli.php:1984 +msgid "You must enter a payment ID to display legacy data." +msgstr "" + +#: includes/class-edd-cli.php:1999 +msgid "The legacy payment data could not be found." +msgstr "" + +#: includes/class-edd-cli.php:2042 +msgid "Sales and Earnings successfully recalculated for all downloads." +msgstr "" + +#: includes/class-edd-cli.php:2043 +msgid "Downloads Updated: " +msgstr "" + +#: includes/class-edd-cli.php:2069 +msgid "Sales and Earnings successfully recalculated for all customers." +msgstr "" + +#: includes/class-edd-cli.php:2070 +msgid "Customers Updated: " +msgstr "" + +#: includes/class-edd-cli.php:2088 +msgid "Do you want to remove legacy data? This will permanently remove legacy discounts, logs, and order notes." +msgstr "" + +#: includes/class-edd-cli.php:2096 +msgid "Legacy discounts have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2098 +msgid "Removing old discount data." +msgstr "" + +#: includes/class-edd-cli.php:2119 +msgid "Legacy logs have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2121 +msgid "Removing old logs." +msgstr "" + +#: includes/class-edd-cli.php:2142 +msgid "Legacy order notes have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2144 +msgid "Removing old order notes." +msgstr "" + +#: includes/class-edd-cli.php:2168 +msgid "Updating customers database table." +msgstr "" + +#: includes/class-edd-cli.php:2171 +msgid "Removing Payment IDs column." +msgstr "" + +#: includes/class-edd-cli.php:2177 +msgid "Removing notes column." +msgstr "" + +#: includes/class-edd-cli.php:2187 +msgid "Legacy customer emails have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2189 +msgid "Removing old customer emails." +msgstr "" + +#: includes/class-edd-cli.php:2200 +msgid "Legacy customer addresses have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2202 +msgid "Removing old customer addresses." +msgstr "" + +#: includes/class-edd-cli.php:2213 +msgid "Legacy orders have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2215 +msgid "Removing old orders." +msgstr "" + +#. translators: %s: discount property name +#: includes/class-edd-discount.php:377 +#: includes/class-edd-download.php:377 +msgid "Can't get property %s" +msgstr "" + +#: includes/class-edd-discount.php:694 +msgid "Draft" +msgstr "" + +#: includes/class-edd-discount.php:697 +#: includes/gateways/stripe/includes/template-functions.php:391 +#: includes/misc-functions.php:1543 +msgid "Expired" +msgstr "" + +#: includes/class-edd-discount.php:1306 +msgctxt "error shown when attempting to use a discount before its start date" +msgid "This discount is invalid." +msgstr "" + +#: includes/class-edd-discount.php:1373 +msgid "This discount has reached its maximum usage." +msgstr "" + +#. translators: %s: Minimum order amount, formatted for the currency +#: includes/class-edd-discount.php:1408 +msgid "Minimum order of %s not met." +msgstr "" + +#: includes/class-edd-discount.php:1497 +msgid "The product requirements for this discount are not met." +msgstr "" + +#: includes/class-edd-discount.php:1513 +#: includes/class-edd-discount.php:1570 +msgid "This discount is not valid for the cart contents." +msgstr "" + +#: includes/class-edd-discount.php:1645 +msgid "This discount has already been redeemed." +msgstr "" + +#: includes/class-edd-discount.php:1667 +#: includes/class-edd-discount.php:1698 +#: includes/discount-functions.php:1019 +msgctxt "error for when a discount is invalid based on its configuration" +msgid "This discount is invalid." +msgstr "" + +#: includes/class-edd-discount.php:1722 +msgid "This discount is expired." +msgstr "" + +#: includes/class-edd-discount.php:1729 +msgid "This discount is not active." +msgstr "" + +#: includes/class-edd-download.php:397 +msgid "New Download Product" +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: includes/class-edd-license-handler.php:302 +#: 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:324 +#: src/Extensions/Handler.php:275 +msgid "Enter valid license key for automatic updates." +msgstr "" + +#: includes/class-edd-register-meta.php:82 +msgid "The total earnings for the specified product" +msgstr "" + +#: includes/class-edd-register-meta.php:93 +msgid "The number of sales for the specified product." +msgstr "" + +#: includes/class-edd-register-meta.php:104 +msgid "The price of the product." +msgstr "" + +#: includes/class-edd-register-meta.php:121 +msgid "An array of variable prices for the product." +msgstr "" + +#: includes/class-edd-register-meta.php:153 +msgid "The files associated with the product, available for download." +msgstr "" + +#: includes/class-edd-register-meta.php:165 +msgid "An array of product IDs to associate with a bundle." +msgstr "" + +#: includes/class-edd-register-meta.php:184 +msgid "Defines how this product's 'Purchase' button should behave, either add to cart or buy now" +msgstr "" + +#: includes/class-edd-register-meta.php:196 +msgid "When variable pricing is enabled, this value defines which option should be chosen by default." +msgstr "" + +#: includes/class-edd-register-meta.php:218 +msgid "The email address associated with the purchase." +msgstr "" + +#: includes/class-edd-register-meta.php:229 +msgid "The Customer ID associated with the payment." +msgstr "" + +#: includes/class-edd-register-meta.php:240 +msgid "The User ID associated with the payment." +msgstr "" + +#: includes/class-edd-register-meta.php:250 +msgid "The IP address the payment was made from." +msgstr "" + +#: includes/class-edd-register-meta.php:260 +msgid "The unique purchase key for this payment." +msgstr "" + +#: includes/class-edd-register-meta.php:270 +msgid "The purchase total for this payment." +msgstr "" + +#: includes/class-edd-register-meta.php:280 +msgid "Identifies if the purchase was made in Test or Live mode." +msgstr "" + +#: includes/class-edd-register-meta.php:290 +msgid "The registered gateway that was used to process this payment." +msgstr "" + +#: includes/class-edd-register-meta.php:300 +msgid "Array of payment meta that contains cart details, downloads, amounts, taxes, discounts, and subtotals, etc." +msgstr "" + +#: includes/class-edd-register-meta.php:310 +msgid "The total amount of tax paid for this payment." +msgstr "" + +#: includes/class-edd-register-meta.php:320 +msgid "The date this payment was changed to the `completed` status." +msgstr "" + +#: includes/class-edd-roles.php:47 +msgid "Shop Manager" +msgstr "" + +#: includes/class-edd-roles.php:82 +msgid "Shop Accountant" +msgstr "" + +#: includes/class-edd-roles.php:92 +msgid "Shop Worker" +msgstr "" + +#: includes/class-edd-roles.php:103 +msgid "Shop Vendor" +msgstr "" + +#: includes/class-edd-roles.php:339 +msgid "Reset EDD User Roles" +msgstr "" + +#: includes/class-edd-stats.php:80 +#: includes/reports/reports-functions.php:537 +msgid "Yesterday" +msgstr "" + +#: includes/class-edd-stats.php:81 +#: includes/reports/reports-functions.php:538 +msgid "This Week" +msgstr "" + +#: includes/class-edd-stats.php:82 +#: includes/reports/reports-functions.php:539 +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:544 +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:546 +msgid "Last Year" +msgstr "" + +#: includes/class-edd-stats.php:428 +msgid "Improper date provided." +msgstr "" + +#: includes/class-stats.php:468 +msgid "Sunday" +msgstr "" + +#: includes/class-stats.php:469 +msgid "Monday" +msgstr "" + +#: includes/class-stats.php:470 +msgid "Tuesday" +msgstr "" + +#: includes/class-stats.php:471 +msgid "Wednesday" +msgstr "" + +#: includes/class-stats.php:472 +msgid "Thursday" +msgstr "" + +#: includes/class-stats.php:473 +msgid "Friday" +msgstr "" + +#: includes/class-stats.php:474 +msgid "Saturday" +msgstr "" + +#: includes/class-stats.php:3224 +#: includes/emails/email-summary/class-edd-email-summary.php:275 +msgid "No Change" +msgstr "" + +#: includes/class-stats.php:3236 +#: includes/emails/email-summary/class-edd-email-summary.php:273 +msgid "No data to compare" +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:325 +#: includes/payments/class-payments-query.php:619 +msgid "Do not use -1 to retrieve all results." +msgstr "" + +#: includes/deprecated-functions.php:188 +msgid "You have already purchased this item, but you may purchase it again." +msgstr "" + +#: includes/deprecated-functions.php:269 +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:270 +#: includes/deprecated-functions.php:303 +msgid "A list of download links for each download purchased" +msgstr "" + +#: includes/deprecated-functions.php:271 +#: includes/deprecated-functions.php:304 +msgid "A plain-text list of download URLs for each download purchased" +msgstr "" + +#: includes/deprecated-functions.php:272 +#: includes/deprecated-functions.php:305 +msgid "The buyer's first name" +msgstr "" + +#: includes/deprecated-functions.php:273 +#: includes/deprecated-functions.php:306 +msgid "The buyer's full name, first and last" +msgstr "" + +#: includes/deprecated-functions.php:274 +#: includes/deprecated-functions.php:307 +msgid "The buyer's user name on the site, if they registered an account" +msgstr "" + +#: includes/deprecated-functions.php:275 +#: includes/deprecated-functions.php:308 +msgid "The buyer's email address" +msgstr "" + +#: includes/deprecated-functions.php:276 +#: includes/deprecated-functions.php:309 +msgid "The buyer's billing address" +msgstr "" + +#: includes/deprecated-functions.php:277 +#: includes/deprecated-functions.php:310 +msgid "The date of the purchase" +msgstr "" + +#: includes/deprecated-functions.php:278 +#: includes/deprecated-functions.php:311 +msgid "The price of the purchase before taxes" +msgstr "" + +#: includes/deprecated-functions.php:279 +#: includes/deprecated-functions.php:312 +#: src/Emails/Tags/Registry.php:127 +msgid "The taxed amount of the purchase" +msgstr "" + +#: includes/deprecated-functions.php:280 +#: includes/deprecated-functions.php:313 +#: src/Emails/Tags/Registry.php:148 +msgid "The total price of the purchase" +msgstr "" + +#: includes/deprecated-functions.php:281 +#: includes/deprecated-functions.php:314 +msgid "The unique ID number for this purchase" +msgstr "" + +#: includes/deprecated-functions.php:282 +#: includes/deprecated-functions.php:315 +msgid "The unique ID number for this purchase receipt" +msgstr "" + +#: includes/deprecated-functions.php:283 +#: includes/deprecated-functions.php:316 +msgid "The method of payment used for this purchase" +msgstr "" + +#: includes/deprecated-functions.php:284 +#: includes/deprecated-functions.php:317 +msgid "Your site name" +msgstr "" + +#: includes/deprecated-functions.php:285 +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:302 +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:489 +#: includes/process-download.php:942 +msgid "Sorry but you have hit your download limit for this file." +msgstr "" + +#: includes/deprecated-functions.php:506 +#: includes/download-functions.php:1521 +msgid "Sorry but your download link has expired." +msgstr "" + +#: includes/deprecated-functions.php:514 +msgid "No payments matching your request were found." +msgstr "" + +#: includes/deprecated-functions.php:916 +msgid "You do not have permission to access this report" +msgstr "" + +#. translators: 1: Old status, 2: New status +#: includes/deprecated-functions.php:1158 +#: includes/orders/functions/transitions.php:30 +msgid "Status changed from %1$s to %2$s" +msgstr "" + +#: includes/deprecated-functions.php:1196 +msgid "Refund Payment in PayPal" +msgstr "" + +#: includes/deprecated-functions.php:1287 +msgid "Disconnect Jilt" +msgstr "" + +#. translators: 1: tag, 2. tag +#: includes/deprecated-functions.php:1295 +msgid "%1$sClick here%2$s to visit your Jilt dashboard" +msgstr "" + +#: includes/deprecated-functions.php:1307 +msgid "Connect to Jilt" +msgstr "" + +#: includes/deprecated-functions.php:1317 +msgid "Install Jilt" +msgstr "" + +#: includes/deprecated-functions.php:1338 +#: includes/deprecated-functions.php:1417 +#: includes/deprecated-functions.php:1446 +#: includes/deprecated-functions.php:1596 +#: includes/deprecated-functions.php:1672 +#: includes/emails/recapture.php:16 +msgid "You do not have permission to do this." +msgstr "" + +#: includes/deprecated-functions.php:1396 +#: includes/deprecated-functions.php:1425 +msgid "Something went wrong. Jilt was not installed correctly." +msgstr "" + +#. translators: 1: tag, 2. tag, 3. tag, 4. tag +#: includes/deprecated-functions.php:1487 +msgid "%1$sRecover abandoned purchases like this one.%2$s %3$sTry Jilt for free%4$s." +msgstr "" + +#. translators: 1: Opening anchor tag, 2: The url to dismiss the ajax notice, 3: Complete the opening of the anchor tag, 4: Open span tag, 5: Close span tag +#: includes/deprecated-functions.php:1500 +#: includes/emails/recapture.php:126 +msgid "%1$s %2$s %3$s %4$s Dismiss this notice. %5$s" +msgstr "" + +#: includes/deprecated-functions.php:1537 +msgid "Access your SendWP account" +msgstr "" + +#: includes/deprecated-functions.php:1541 +msgid "Note: Email sending is currently disabled. Product Reviews extension." +msgstr "" + +#. translators: %s: The site name +#: includes/deprecated-functions.php:2494 +msgid "[%s] New User Registration" +msgstr "" + +#: includes/deprecated-functions.php:2495 +#: src/Emails/Templates/NewUserAdmin.php:86 +msgid "New user registration" +msgstr "" + +#. translators: the user email +#: includes/deprecated-functions.php:2499 +msgid "E-mail: %s" +msgstr "" + +#. translators: Site name +#: includes/deprecated-functions.php:2509 +msgid "[%s] Your username and password" +msgstr "" + +#: includes/deprecated-functions.php:2510 +#: src/Emails/Templates/NewUser.php:86 +msgid "Your account info" +msgstr "" + +#: includes/deprecated-functions.php:2515 +msgid "Password entered at checkout" +msgstr "" + +#: includes/deprecated-functions.php:2517 +msgid "Password entered at registration" +msgstr "" + +#. translators: %s: password message +#: includes/deprecated-functions.php:2521 +msgid "Password: %s" +msgstr "" + +#: includes/deprecated-functions.php:2526 +#: src/Emails/Templates/NewUser.php:114 +msgid "Click here to log in" +msgstr "" + +#. translators: %s: login URL +#: includes/deprecated-functions.php:2530 +#: src/Emails/Templates/NewUser.php:118 +msgid "To log in, visit: %s" +msgstr "" + +#. translators: 1: Function name, 2: Version number, 3: Replacement function name +#: includes/discount-functions.php:505 +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 "" + +#: includes/download-functions.php:607 +msgid "Service" +msgstr "" + +#. translators: the plugin name. +#: includes/EDD_SL_Plugin_Updater.php:288 +#: src/Extensions/Updater.php:315 +msgid "There is a new version of %1$s available." +msgstr "" + +#: includes/EDD_SL_Plugin_Updater.php:294 +#: src/Extensions/Updater.php:321 +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:299 +#: src/Extensions/Updater.php:326 +msgid "%1$sView version %2$s details%3$s." +msgstr "" + +#. translators: 1: opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate 4. opening anchor tag, do not translate 5. closing anchor tag, do not translate. +#: includes/EDD_SL_Plugin_Updater.php:307 +#: src/Extensions/Updater.php:335 +msgid "%1$sView version %2$s details%3$s or %4$supdate now%5$s." +msgstr "" + +#: includes/EDD_SL_Plugin_Updater.php:318 +#: src/Extensions/Updater.php:346 +msgid "Update now." +msgstr "" + +#: includes/EDD_SL_Plugin_Updater.php:553 +#: src/Extensions/Updater.php:510 +msgid "You do not have permission to install plugin updates" +msgstr "" + +#. translators: Site domain name +#: includes/emails/email-summary/class-edd-email-summary.php:96 +msgid "Easy Digital Downloads Summary - %s" +msgstr "" + +#: includes/emails/email-summary/class-edd-email-summary.php:128 +msgid "Missing email recipients for Email Summary" +msgstr "" + +#: includes/emails/email-summary/class-edd-email-summary.php:318 +msgid "month" +msgstr "" + +#: includes/emails/email-summary/class-edd-email-summary.php:318 +msgid "week" +msgstr "" + +#. translators: period name (e.g. week) +#: includes/emails/email-summary/class-edd-email-summary.php:320 +msgid "vs previous %s" +msgstr "" + +#: includes/emails/email-summary/class-edd-email-summary.php:348 +msgid "Email body for Email Summary was empty." +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:43 +msgid "View Full Report" +msgstr "" + +#: includes/emails/email-summary/template-parts/data-listing.php:16 +#: includes/emails/email-summary/template-parts/top-products.php:18 +msgid "Gross Revenue" +msgstr "" + +#: includes/emails/email-summary/template-parts/data-listing.php:92 +msgid "Average Order" +msgstr "" + +#. translators: 1: amount that could have been saved, 2: opening anchor tag, 3: closing anchor tag +#: includes/emails/email-summary/template-parts/fee-info.php:59 +msgid "You could have saved %1$s in transaction fees this year by %2$supgrading to an Extended Pass%3$s." +msgstr "" + +#. translators: 1: opening span tag, 2. the formatted currency amount, 3. the closing span tag +#: includes/emails/email-summary/template-parts/fee-info.php:67 +msgid "You have %1$ssaved %2$s in transaction fees%3$s this year with your active license." +msgstr "" + +#: includes/emails/email-summary/template-parts/preview-text.php:7 +msgid "Store performance summary" +msgstr "" + +#: includes/emails/email-summary/template-parts/pro-tips.php:18 +msgid "Pro-tip from our expert" +msgstr "" + +#: includes/emails/email-summary/template-parts/site-info.php:6 +msgid "Your eCommerce Summary" +msgstr "" + +#: includes/emails/email-summary/template-parts/site-info.php:18 +msgid "Hey there!" +msgstr "" + +#: includes/emails/email-summary/template-parts/site-info.php:31 +msgid "Below is a look at how your store performed in the last month." +msgstr "" + +#: includes/emails/email-summary/template-parts/site-info.php:34 +msgid "Below is a look at how your store performed in the last week." +msgstr "" + +#: includes/emails/email-summary/template-parts/site-info.php:38 +msgid "Below is a look at how your store has been performing." +msgstr "" + +#: includes/emails/email-summary/template-parts/top-products.php:12 +msgid "Top 5 Products by Revenue" +msgstr "" + +#: includes/emails/recapture.php:84 +msgid "Something went wrong. Recapture for EDD was not installed correctly." +msgstr "" + +#. translators: 1: tag, 2. tag, 3. tag, 4. tag +#: includes/emails/recapture.php:113 +msgid "%1$sRecover abandoned purchases like this one.%2$s %3$sTry Recapture for free%4$s." +msgstr "" + +#: includes/emails/tags-inserter.php:80 +#: src/Admin/Onboarding/Steps/ConfigureEmails.php:110 +msgid "Insert Tag" +msgstr "" + +#: includes/emails/tags-inserter.php:90 +msgid "Restore Default" +msgstr "" + +#: includes/emails/tags-inserter.php:98 +msgid "This email will be sent as a plain text email and does not support images or HTML markup." +msgstr "" + +#: includes/emails/tags-inserter.php:100 +msgid "This is specific to this email and does not affect other emails." +msgstr "" + +#: includes/emails/tags-inserter.php:169 +msgid "Find a tag..." +msgstr "" + +#: includes/emails/tags.php:300 +msgid "Additional information about your purchase:" +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: includes/emails/tags.php:765 +msgid "%1$sView it in your browser %2$s" +msgstr "" + +#: includes/emails/template.php:61 +msgid "These are some sample notes added to a product." +msgstr "" + +#: includes/emails/template.php:109 +msgid "Missing purchase key." +msgstr "" + +#: includes/formatting.php:338 +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 "" + +#. translators: %1$s Opening anchor tag, %2$s Closing anchor tag, %3$s Opening anchor tag +#: includes/gateways/amazon-payments.php:121 +msgid "Amazon Payments for Easy Digital Downloads has been deprecated and will be removed in a future version. To continue to accept credit card payments in the future, please %1$senable Stripe now%2$s or %3$slearn more about the benefits of using Stripe%2$s." +msgstr "" + +#: includes/gateways/amazon-payments.php:224 +msgid "There is an error with the Amazon Payments configuration." +msgstr "" + +#: includes/gateways/amazon-payments.php:294 +#: includes/gateways/amazon-payments.php:295 +msgid "Amazon" +msgstr "" + +#: includes/gateways/amazon-payments.php:344 +msgid "Amazon Payments" +msgstr "" + +#: includes/gateways/amazon-payments.php:360 +msgid "Register with Amazon" +msgstr "" + +#: includes/gateways/amazon-payments.php:362 +msgid "Connect Easy Digital Downloads to Amazon" +msgstr "" + +#: includes/gateways/amazon-payments.php:365 +msgid "Once registration is complete, enter your API credentials below." +msgstr "" + +#: includes/gateways/amazon-payments.php:371 +msgid "Seller ID" +msgstr "" + +#: includes/gateways/amazon-payments.php:372 +msgid "Found in the Integration settings. Also called a Merchant ID" +msgstr "" + +#: includes/gateways/amazon-payments.php:378 +msgid "MWS Access Key" +msgstr "" + +#: includes/gateways/amazon-payments.php:379 +#: includes/gateways/amazon-payments.php:386 +msgid "Found on Seller Central in the MWS Keys section" +msgstr "" + +#: includes/gateways/amazon-payments.php:385 +msgid "MWS Secret Key" +msgstr "" + +#: includes/gateways/amazon-payments.php:392 +msgid "Client ID" +msgstr "" + +#: includes/gateways/amazon-payments.php:393 +msgid "The Amazon Client ID. Should look like `amzn1.application-oa2...`" +msgstr "" + +#: includes/gateways/amazon-payments.php:399 +msgid "Amazon MWS Callback URL" +msgstr "" + +#: includes/gateways/amazon-payments.php:400 +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:408 +msgid "Amazon Merchant IPN URL" +msgstr "" + +#. translators: %s: Integration Settings URL +#: includes/gateways/amazon-payments.php:410 +msgid "The IPN URL to provide in your MWS account. Enter this under your Integration Settings" +msgstr "" + +#: includes/gateways/amazon-payments.php:694 +msgid "Currently logged into Amazon as" +msgstr "" + +#: includes/gateways/amazon-payments.php:695 +msgid "Logout" +msgstr "" + +#: includes/gateways/amazon-payments.php:845 +#: includes/gateways/amazon-payments.php:859 +msgid "Missing Reference ID, please try again." +msgstr "" + +#. translators: %s: Payment Failure Reason (dynamic, provided by the gateway) +#: includes/gateways/amazon-payments.php:893 +msgid "Your payment could not be authorized, please try a different payment method. Reason: %s" +msgstr "" + +#. translators: %s: Amazon Error (dynamic, provided by the gateway) +#: includes/gateways/amazon-payments.php:940 +msgid "There was an issue processing your payment. Amazon error: %s" +msgstr "" + +#: includes/gateways/amazon-payments.php:1055 +msgid "Invalid Amazon seller ID" +msgstr "" + +#: includes/gateways/amazon-payments.php:1056 +#: includes/gateways/amazon-payments.php:1100 +#: includes/gateways/paypal-standard.php:496 +#: includes/gateways/paypal-standard.php:507 +#: includes/gateways/paypal-standard.php:603 +#: includes/gateways/paypal-standard.php:618 +#: includes/gateways/paypal-standard.php:698 +#: includes/gateways/paypal-standard.php:712 +#: src/Gateways/PayPal/IPN.php:199 +#: src/Gateways/PayPal/IPN.php:210 +msgid "IPN Error" +msgstr "" + +#: includes/gateways/amazon-payments.php:1077 +msgid "Capture declined in Amazon" +msgstr "" + +#. translators: %s: Amazon Refund ID +#: includes/gateways/amazon-payments.php:1092 +#: includes/gateways/amazon-payments.php:1172 +msgid "Refund completed in Amazon. Refund ID: %s" +msgstr "" + +#. translators: %s: Amazon Refund ID +#: includes/gateways/amazon-payments.php:1166 +msgid "Refund declined in Amazon. Refund ID: %s" +msgstr "" + +#. translators: %s: Amazon Refund ID +#: includes/gateways/amazon-payments.php:1177 +msgid "Refund initiated in Amazon. Reference ID: %s" +msgstr "" + +#: includes/gateways/amazon-payments.php:1183 +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:35 +msgid "PayPal Standard" +msgstr "" + +#: includes/gateways/functions.php:339 +msgid "The requested price ID does not exist." +msgstr "" + +#: includes/gateways/manual.php:33 +#: includes/gateways/paypal-standard.php:172 +#: includes/gateways/paypal/checkout-actions.php:118 +msgid "Nonce verification has failed" +msgstr "" + +#. translators: %s: payment data +#: includes/gateways/manual.php:72 +msgid "Payment creation failed while processing a manual (free or test) purchase. Payment data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:57 +msgid "PayPal Email" +msgstr "" + +#: includes/gateways/paypal-standard.php:58 +msgid "Enter your PayPal account's email" +msgstr "" + +#: includes/gateways/paypal-standard.php:64 +msgid "PayPal Image" +msgstr "" + +#: includes/gateways/paypal-standard.php:65 +msgid "Upload an image to display on the PayPal checkout page." +msgstr "" + +#. translators: %s: Documentation URL +#: 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 "" + +#. translators: %s: FAQ URL +#: includes/gateways/paypal-standard.php:87 +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:93 +msgid "Disable PayPal IPN Verification" +msgstr "" + +#: includes/gateways/paypal-standard.php:102 +msgid "API Credentials" +msgstr "" + +#. translators: %s: PayPal API Credentials URL +#: includes/gateways/paypal-standard.php:106 +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:112 +msgid "Live API Username" +msgstr "" + +#: includes/gateways/paypal-standard.php:113 +msgid "Your PayPal live API username. " +msgstr "" + +#: includes/gateways/paypal-standard.php:119 +msgid "Live API Password" +msgstr "" + +#: includes/gateways/paypal-standard.php:120 +msgid "Your PayPal live API password." +msgstr "" + +#: includes/gateways/paypal-standard.php:126 +msgid "Live API Signature" +msgstr "" + +#: includes/gateways/paypal-standard.php:127 +msgid "Your PayPal live API signature." +msgstr "" + +#: includes/gateways/paypal-standard.php:133 +msgid "Test API Username" +msgstr "" + +#: includes/gateways/paypal-standard.php:134 +msgid "Your PayPal test API username." +msgstr "" + +#: includes/gateways/paypal-standard.php:140 +msgid "Test API Password" +msgstr "" + +#: includes/gateways/paypal-standard.php:141 +msgid "Your PayPal test API password." +msgstr "" + +#: includes/gateways/paypal-standard.php:147 +msgid "Test API Signature" +msgstr "" + +#: includes/gateways/paypal-standard.php:148 +msgid "Your PayPal test API signature." +msgstr "" + +#. translators: %s: payment data +#: includes/gateways/paypal-standard.php:197 +msgid "Payment creation failed before sending buyer to PayPal. Payment data: %s" +msgstr "" + +#. translators: %s: IPN Verification response +#: includes/gateways/paypal-standard.php:498 +#: includes/gateways/paypal-standard.php:509 +msgid "Invalid IPN verification response. IPN data: %s" +msgstr "" + +#. translators: %s: IPN Verification response +#: includes/gateways/paypal-standard.php:605 +msgid "Invalid business email in IPN response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:610 +msgid "Payment failed due to invalid PayPal business email." +msgstr "" + +#. translators: %s: IPN Verification response +#: includes/gateways/paypal-standard.php:620 +msgid "Invalid currency in IPN response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:625 +msgid "Payment failed due to invalid currency in PayPal IPN." +msgstr "" + +#. translators: %s: IPN Verification response +#: includes/gateways/paypal-standard.php:700 +msgid "Invalid payment amount in IPN response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:705 +msgid "Payment failed due to invalid amount in PayPal IPN." +msgstr "" + +#. translators: %s: IPN Verification response +#: includes/gateways/paypal-standard.php:714 +msgid "Invalid purchase key in IPN response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:718 +msgid "Payment failed due to invalid purchase key in PayPal IPN." +msgstr "" + +#. translators: %s: PayPal Transaction ID +#: includes/gateways/paypal-standard.php:724 +#: includes/gateways/paypal/checkout-actions.php:420 +msgid "PayPal Transaction ID: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:737 +msgid "Payment made via eCheck and will clear automatically in 5-8 days" +msgstr "" + +#: includes/gateways/paypal-standard.php:743 +msgid "Payment requires a confirmed customer address and must be accepted manually through PayPal" +msgstr "" + +#: includes/gateways/paypal-standard.php:748 +msgid "Payment must be accepted manually through PayPal due to international account regulations" +msgstr "" + +#: includes/gateways/paypal-standard.php:753 +msgid "Payment received in non-shop currency and must be accepted manually through PayPal" +msgstr "" + +#: includes/gateways/paypal-standard.php:759 +msgid "Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations" +msgstr "" + +#: includes/gateways/paypal-standard.php:764 +msgid "Payment was sent to non-confirmed or non-registered email address." +msgstr "" + +#: includes/gateways/paypal-standard.php:769 +msgid "PayPal account must be upgraded before this payment can be accepted" +msgstr "" + +#: includes/gateways/paypal-standard.php:774 +msgid "PayPal account is not verified. Verify account in order to accept this payment" +msgstr "" + +#: includes/gateways/paypal-standard.php:779 +msgid "Payment is pending for unknown reasons. Contact PayPal support for assistance" +msgstr "" + +#. translators: %s: PayPal transaction ID +#: includes/gateways/paypal-standard.php:831 +msgid "Partial PayPal refund processed: %s" +msgstr "" + +#. translators: 1: PayPal transaction ID, 2: Reason for refund +#: includes/gateways/paypal-standard.php:837 +msgid "PayPal Payment #%1$s Refunded for reason: %2$s" +msgstr "" + +#. translators: %s: PayPal transaction ID +#: includes/gateways/paypal-standard.php:839 +msgid "PayPal Refund Transaction ID: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:1051 +msgid "Payment could not be verified while validating PayPal PDT. Missing payment total fields." +msgstr "" + +#. translators: 1: Expected payment amount, 2: Received payment amount +#: includes/gateways/paypal-standard.php:1063 +msgid "Payment failed while validating PayPal PDT. Amount expected: %1$f. Amount Received: %2$f" +msgstr "" + +#: includes/gateways/paypal-standard.php:1092 +msgid "Payment failed while validating PayPal PDT." +msgstr "" + +#: includes/gateways/paypal-standard.php:1099 +msgid "PayPal PDT encountered an unexpected result, payment set to pending" +msgstr "" + +#: includes/gateways/paypal-standard.php:1188 +#: includes/gateways/paypal/refunds.php:50 +msgid "Refund transaction in PayPal" +msgstr "" + +#: includes/gateways/paypal-standard.php:1228 +#: includes/gateways/paypal/refunds.php:93 +msgid "Transaction not refunded in PayPal, as checkbox was not selected." +msgstr "" + +#: includes/gateways/paypal-standard.php:1352 +msgid "PayPal refund failed for unknown reason." +msgstr "" + +#. translators: 1: amount refunded; %2$s - transaction ID. +#: includes/gateways/paypal-standard.php:1364 +msgid "%1$s refunded in PayPal. Transaction ID: %2$s" +msgstr "" + +#. translators: %s: PayPal Transaction ID. +#: includes/gateways/paypal-standard.php:1367 +msgid "PayPal refund transaction ID: %s" +msgstr "" + +#. translators: %s: error message. +#: includes/gateways/paypal-standard.php:1404 +msgid "PayPal refund failed: %s" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:35 +#: includes/gateways/paypal/admin/connect.php:466 +msgid "sandbox" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:35 +#: includes/gateways/paypal/admin/connect.php:466 +msgid "live" +msgstr "" + +#. translators: 1. opening tag, 2. closing tag +#: includes/gateways/paypal/admin/connect.php:47 +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:62 +msgid "Connect with PayPal in %s mode" +msgstr "" + +#. translators: 1: HTTP response code, 2: error message +#: includes/gateways/paypal/admin/connect.php:228 +msgid "Unexpected response code: %1$d. Error: %2$s" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:236 +msgid "An unexpected error occurred." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:268 +msgid "Failure reconnecting to PayPal. Please try again" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:274 +msgid "Your account has been successfully reconnected, but an error occurred while creating a webhook." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:311 +msgid "Missing PayPal authentication information. Please try again." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:325 +msgid "Missing nonce. Please refresh the page and try again." +msgstr "" + +#. translators: %d: HTTP response code +#: includes/gateways/paypal/admin/connect.php:363 +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:396 +msgid "Unexpected response from PayPal. Response code: %d. Please try again." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:405 +msgid "Successfully connected." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:410 +msgid "Your account has been successfully connected, but an error occurred while creating a webhook." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:444 +msgid "Re-Check Payment Status" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:445 +msgid "Sync Webhook" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:449 +msgid "Disconnect webhooks from PayPal" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:450 +msgid "Delete connection with PayPal" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:459 +msgid "API:" +msgstr "" + +#. translators: %s: the connected mode, either `sandbox` or `live` +#: includes/gateways/paypal/admin/connect.php:469 +msgid "Your PayPal account is successfully connected in %s mode." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:484 +msgid "Payment Status:" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:488 +msgid "You need to address the following issues before you can start receiving payments:" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:496 +msgid "Ready to accept payments." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:518 +msgid "Webhook:" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:526 +msgid "Create Webhooks" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:531 +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:556 +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:742 +msgid "No merchant ID saved. Please reconnect to PayPal." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:828 +msgid "There was an error processing the connection to PayPal. Please attempt to connect again." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:832 +msgid "Return to settings" +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:35 +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 +#: src/Gateways/Stripe/Admin/Settings.php:35 +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 +msgctxt "It is important to escape any quotations within this string, specifically \\'Sandhills Development, LLC\\'" +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:135 +msgid "Get Help" +msgstr "" + +#. translators: %s: SSL setup article URL +#: includes/gateways/paypal/admin/settings.php:147 +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:130 +msgid "Please connect your PayPal account in the gateway settings." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:57 +#: includes/gateways/paypal/checkout-actions.php:131 +msgid "Unexpected authentication error. Please contact a site administrator." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:125 +#: includes/gateways/paypal/exceptions/class-gateway-exception.php:48 +msgid "PayPal Gateway Error" +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:126 +msgid "Account not ready to accept payments." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:157 +#: includes/gateways/paypal/checkout-actions.php:324 +#: includes/gateways/paypal/scripts.php:111 +msgid "An unexpected error occurred. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:210 +msgid "PayPal Gateway Warning" +msgstr "" + +#. translators: %s: Original order data sent to PayPal. +#: includes/gateways/paypal/checkout-actions.php:213 +msgid "PayPal could not complete the transaction with the itemized breakdown. Original order data sent: %s" +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:232 +#: includes/gateways/paypal/checkout-actions.php:264 +#: includes/gateways/paypal/checkout-actions.php:442 +msgid "An error occurred while communicating with PayPal. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:262 +#: includes/gateways/paypal/checkout-actions.php:440 +msgid "An authentication error occurred. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:301 +#: includes/gateways/paypal/checkout-actions.php:309 +#: includes/gateways/paypal/checkout-actions.php:316 +msgid "A validation error occurred. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:337 +msgid "Failed to process payment. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:346 +msgid "Unable to complete your order with your chosen payment method. Please choose a new funding source." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:431 +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:146 +msgid "Your account is unable to receive payments. Please contact PayPal customer support." +msgstr "" + +#: includes/gateways/paypal/class-merchant-account.php:152 +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:158 +msgid "Missing PayPal credential: %s" +msgstr "" + +#. translators: %d: HTTP response code. +#: includes/gateways/paypal/class-paypal-api.php:223 +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: 1: Response code, 2: Response message +#: includes/gateways/paypal/exceptions/class-gateway-exception.php:51 +msgid "Response Code: %1$d; Message: %2$s" +msgstr "" + +#: includes/gateways/paypal/gateway-filters.php:101 +msgid "Merchandise or service not received" +msgstr "" + +#: includes/gateways/paypal/gateway-filters.php:102 +msgid "Merchandise or service not as described" +msgstr "" + +#: includes/gateways/paypal/gateway-filters.php:103 +msgid "Unauthorized" +msgstr "" + +#: includes/gateways/paypal/gateway-filters.php:104 +msgid "Item not received" +msgstr "" + +#: includes/gateways/paypal/gateway-filters.php:105 +msgid "Unauthorized transaction" +msgstr "" + +#: includes/gateways/paypal/gateway-filters.php:106 +msgid "Buyer complaint" +msgstr "" + +#: includes/gateways/paypal/refunds.php:55 +msgid "This order is currently on hold. You can create the refund transaction in EDD; PayPal may have already issued a refund." +msgstr "" + +#. translators: 1: Refund ID, 2: Error message +#: includes/gateways/paypal/refunds.php:119 +msgid "Failure when processing PayPal refund #%1$d: %2$s" +msgstr "" + +#: includes/gateways/paypal/refunds.php:161 +msgid "Missing transaction ID." +msgstr "" + +#. translators: 1: Response code, 2: Response message +#: includes/gateways/paypal/refunds.php:187 +msgid "Unexpected response code: %1$d. Response: %2$s" +msgstr "" + +#. translators: %s: API response from PayPal +#: includes/gateways/paypal/refunds.php:196 +msgid "Missing or unexpected refund status. Response: %s" +msgstr "" + +#. translators: 1: amount refunded; 2: transaction ID. +#: includes/gateways/paypal/refunds.php:209 +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:216 +msgid "Successfully refunded in PayPal. Refund transaction ID: %s" +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:87 +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:132 +msgid "Invalid response code %1$d while creating webhook. Response: %2$s" +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:140 +msgid "Unexpected response from PayPal." +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:170 +msgid "Webhook not configured." +msgstr "" + +#. translators: %d: HTTP response code; %s - Full response from the API. +#: includes/gateways/paypal/webhooks/functions.php:201 +msgid "Invalid response code %1$d while syncing webhook. Response: %2$s" +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:252 +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:258 +msgid "Invalid response code %d while retrieving webhook details." +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:266 +msgid "Unexpected response from PayPal when retrieving webhook details." +msgstr "" + +#. translators: %d: HTTP response code. +#: includes/gateways/paypal/webhooks/functions.php:303 +msgid "Invalid response code %d while deleting webhook." +msgstr "" + +#. translators: %1$s Stripe Connect error message. %2$s Retry URL. +#: includes/gateways/stripe/includes/admin/admin-actions.php:13 +msgid "There was an error connecting your Stripe account. Message: %1$s. Please try again." +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:45 +msgid "Used Existing Card:" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:46 +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:89 +msgid "Yes" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:118 +msgid "Refund Charge in Stripe" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:123 +msgid "This order is currently on hold. You can create the refund transaction in EDD; Stripe may have already issued a refund." +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:54 +msgid "Bank cannot process" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:55 +msgid "Check returned" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:56 +msgid "Credit not processed" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:57 +msgid "Customer initiated" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:58 +msgid "Debit not authorized" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:59 +msgid "Duplicate" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:60 +msgid "Fraudulent" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:62 +msgid "Incorrect account details" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:63 +msgid "Insufficient funds" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:64 +msgid "Product not received" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:65 +msgid "Product unacceptable" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:66 +msgid "Subscription canceled" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:67 +msgid "Unrecognized" +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:692 +#: 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 +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 "" + +#: 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 "" + +#: includes/gateways/stripe/includes/admin/settings.php:11 +msgid "Stripe" +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:129 +#: includes/gateways/stripe/includes/admin/settings.php:202 +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 "" + +#: includes/gateways/stripe/includes/admin/settings.php:147 +#: includes/gateways/stripe/includes/admin/settings.php:217 +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/settings.php:153 +#: includes/gateways/stripe/includes/admin/settings.php:222 +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:126 +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:345 +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:636 +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:698 +#: includes/gateways/stripe/includes/admin/site-health.php:94 +msgid "Connect with Stripe" +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:354 +msgid "Have questions about connecting with Stripe? See the %1$sdocumentation%2$s." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:395 +msgid "Manage API keys manually" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:405 +msgid "Hide API keys" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:411 +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:429 +msgid "Unable to retrieve account information." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:447 +msgctxt "Stripe Connect mode" +msgid "test" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:448 +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:454 +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:477 +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:483 +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:491 +msgid "%1$sRegister a Stripe account%2$s for full access." +msgstr "" + +#. translators: 1: Opening anchor tag for disconnecting Stripe, do not translate. 2: Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:498 +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:623 +msgid "%1$sDisconnect this account%2$s." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:570 +msgid "Administrator (Owner)" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:604 +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:639 +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:652 +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:668 +msgid "Your manually managed %1$s mode API keys are invalid." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:708 +#: 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:719 +msgctxt "gateway test mode status" +msgid "enabled" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:720 +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:729 +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:62 +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:65 +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:67 +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:413 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:33 +#: 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:231 +#: includes/gateways/stripe/includes/template-functions.php:183 +#: src/Gateways/Stripe/PaymentMethods/Card.php:58 +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:122 +#: includes/process-purchase.php:34 +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:127 +#: includes/gateways/stripe/includes/compat.php:132 +#: includes/gateways/stripe/includes/compat.php:141 +#: includes/process-purchase.php:39 +msgid "Error processing purchase. Please reload the page and try again." +msgstr "" + +#: includes/gateways/stripe/includes/deprecated.php:375 +#: includes/process-purchase.php:489 +#: includes/process-purchase.php:869 +msgid "The user information is invalid" +msgstr "" + +#: includes/gateways/stripe/includes/elements/payment-elements.php:384 +msgid "Please enter your billing address." +msgstr "" + +#: includes/gateways/stripe/includes/elements/payment-elements.php:389 +msgid "Please enter your last name." +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:588 +msgid "Invalid order ID." +msgstr "" + +#. translators: %1$s the amount refunded; %2$s Stripe Refund ID +#: includes/gateways/stripe/includes/functions.php:658 +msgid "%1$s refunded in Stripe. Refund ID %2$s" +msgstr "" + +#: includes/gateways/stripe/includes/gateway-actions.php:13 +msgid "Outputs a line stating what charges will appear as on customer's credit card statements." +msgstr "" + +#: includes/gateways/stripe/includes/gateway-actions.php:15 +#: src/Gateways/Stripe/Admin/Settings.php:87 +msgid "Statement Descriptor" +msgstr "" + +#. translators: %s: statement descriptor +#: includes/gateways/stripe/includes/gateway-actions.php:36 +msgid "Charges will appear on your card statement as %s" +msgstr "" + +#: includes/gateways/stripe/includes/gateway-filters.php:60 +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:132 +msgid "Subscriptions and non-subscriptions may not be purchased at the same time. Please purchase each separately." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:138 +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:54 +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:72 +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:90 +#: src/Gateways/Stripe/Checkout/Form.php:382 +msgid "Unable to create customer. Please try again." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:213 +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:362 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:387 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:408 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:490 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:578 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:668 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:761 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:981 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1124 +#: src/Gateways/Stripe/Checkout/Complete.php:88 +msgid "Stripe Error" +msgstr "" + +#. translators: %s: Error message +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:365 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:390 +msgid "There was an error while processing a Stripe payment. Payment data: %s" +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:454 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:464 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:529 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:539 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:616 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:627 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:706 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:716 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:800 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:811 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:823 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:834 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:851 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:882 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1019 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1030 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1040 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1059 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1109 +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:966 +msgid "Unable to create payment." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/functions.php:94 +msgid "Charge not refunded in Stripe, as checkbox was not selected." +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:391 +msgid "Please enter a valid email address" +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:37 +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:46 +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:95 +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:51 +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:57 +msgid "Apple Pay is not available in Test Mode." +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:59 +msgid "See our %1$sdocumentation%2$s for more information." +msgstr "" + +#. translators: %s Download noun +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:80 +msgid "Single %s" +msgstr "" + +#. translators: 1: Download singular label, 2: shortcode tag wrapped in span +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:85 +msgid "%1$s Archive (includes %2$s shortcode)" +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:96 +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:126 +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:134 +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:165 +#: includes/gateways/stripe/includes/payment-methods/payment-request/functions.php:222 +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/scripts.php:92 +msgid "Stripe publishable key missing. Please enter your publishable key in Settings." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:93 +msgid "Please fill out all required fields to continue your purchase." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:94 +msgid "Please agree to the terms to complete your purchase." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:95 +msgid "Please agree to the privacy policy to complete your purchase." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:96 +msgid "Unable to complete your request. Please try again." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:101 +msgid "Please wait..." +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:41 +msgid "Payment Info" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:108 +msgid "Adding new payment methods is currently unavailable." +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:181 +msgid "Credit Card Number" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:207 +msgid "Expiration" +msgstr "" + +#. translators: %1$s Card type. %2$s Card last 4. +#: includes/gateways/stripe/includes/template-functions.php:297 +msgid "Update card billing address for %1$s •••• %2$s" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:325 +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:366 +#: includes/gateways/stripe/includes/template-functions.php:456 +msgid "%1$s •••• %2$s" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:403 +#: includes/gateways/stripe/includes/template-functions.php:696 +msgid "Add New Card" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:433 +msgid "Payment method management is currently unavailable." +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:446 +msgid "Manage Payment Methods" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:475 +msgid "Expires" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:497 +#: src/Admin/Downloads/Editor/VariablePrices.php:293 +msgid "Set as Default" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:515 +#: includes/gateways/stripe/includes/template-functions.php:841 +msgid "Billing Details" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:572 +msgid "ZIP Code" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:677 +#: includes/gateways/stripe/includes/template-functions.php:721 +msgid "Please Wait…" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:678 +#: includes/gateways/stripe/includes/template-functions.php:679 +msgid "Update Card" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:698 +msgid "Credit Card Details" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:722 +#: includes/gateways/stripe/includes/template-functions.php:723 +msgid "Add new card" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:844 +msgid "Billing Country" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:878 +msgid "Billing Zip / Postal Code" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:961 +#: includes/process-purchase.php:407 +msgid "Please enter your zip / postal code" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:966 +#: includes/process-purchase.php:419 +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:111 +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/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: function, 2: version, 3: replacement +#: includes/misc-functions.php:525 +msgctxt "deprecated function error logging with replacement function" +msgid "%1$s is deprecated since Easy Digital Downloads version %2$s! Use %3$s instead." +msgstr "" + +#. translators: 1: function, 2: version +#: includes/misc-functions.php:534 +msgctxt "deprecated function error logging with no replacement" +msgid "%1$s is deprecated since Easy Digital Downloads version %2$s with no alternative available." +msgstr "" + +#. translators: 1: argument, 2: function, 3: version, 4: replacement +#: includes/misc-functions.php:573 +msgctxt "deprecated function argument error logging with replacement" +msgid "The %1$s argument of %2$s is deprecated since Easy Digital Downloads version %3$s! Please use %4$s instead." +msgstr "" + +#. translators: 1: argument, 2: function, 3: version +#: includes/misc-functions.php:582 +msgctxt "deprecated function argument error logging with no replacement" +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, 3: alternative file name +#: includes/misc-functions.php:638 +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:641 +msgid "%1$s is deprecated since Easy Digital Downloads version %2$s with no alternative available." +msgstr "" + +#. translators: 1: PHP file name, 2: EDD version number +#: includes/misc-functions.php:669 +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 +#: includes/misc-functions.php:711 +msgid "The class %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:839 +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:842 +msgid "The %1$s hook is deprecated since Easy Digital Downloads version %2$s with no alternative available." +msgstr "" + +#: includes/misc-functions.php:1320 +msgid "Store Bot" +msgstr "" + +#: includes/misc-functions.php:1547 +msgid "Verified" +msgstr "" + +#: includes/misc-functions.php:1548 +msgid "Spam" +msgstr "" + +#: includes/misc-functions.php:1549 +msgid "Deleted" +msgstr "" + +#: includes/misc-functions.php:1550 +msgid "Cancelled" +msgstr "" + +#: includes/misc-functions.php:1667 +msgid "Please define default parameters in the form of an array." +msgstr "" + +#: includes/misc-functions.php:1672 +msgid "Please define an SVG icon filename." +msgstr "" + +#: includes/orders/functions/orders.php:738 +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:233 +msgid "Invalid argument. Please check your amounts and try again." +msgstr "" + +#: includes/orders/functions/types.php:90 +#: includes/orders/functions/ui.php:36 +#: src/Emails/Templates/Registry.php:113 +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/downloads/edit.js:72 +#: includes/blocks/src/terms/edit.js:62 +msgid "Order" +msgstr "" + +#: includes/orders/functions/types.php:101 +#: src/Emails/Templates/Registry.php:114 +msgid "Refund" +msgstr "" + +#: includes/payments/actions.php:258 +#: includes/payments/actions.php:319 +msgid "Error resuming payment." +msgstr "" + +#: includes/payments/actions.php:262 +msgid "Payment recovery triggered URL" +msgstr "" + +#: includes/payments/actions.php:340 +#: includes/payments/actions.php:374 +msgid "To complete this payment, please login to your account." +msgstr "" + +#: includes/payments/actions.php:342 +#: src/Emails/Templates/PasswordReset.php:133 +msgid "Lost Password" +msgstr "" + +#: includes/payments/class-edd-payment.php:1872 +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/class-edd-payment.php:2817 +msgid "The refund order could not be created, but the order status was manually set to Refunded." +msgstr "" + +#: includes/payments/functions.php:538 +msgid "Processing" +msgstr "" + +#: includes/payments/functions.php:541 +msgid "Partially Refunded" +msgstr "" + +#: includes/payments/functions.php:1936 +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 "" + +#. translators: %s: Download singular label +#: includes/post-types.php:48 +msgctxt "Text for adding a new Download/Product" +msgid "Add New %s" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:50 +msgctxt "Text for editing an existing Download/Product" +msgid "Edit %s" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:52 +msgctxt "Text for adding a new Download/Product" +msgid "New %s" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:55 +msgctxt "Text for viewing an existing Download/Product" +msgid "View %s" +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/post-types.php:57 +msgctxt "Verb: Text hinting the user to search for Downloads/Products" +msgid "Search %s" +msgstr "" + +#. translators: %s: Downloads plural label +#: includes/post-types.php:59 +msgctxt "Context: When no search results are found for Downloads/Products" +msgid "No %s found" +msgstr "" + +#. translators: %s: Download plural label +#: includes/post-types.php:61 +msgctxt "Context: When no Downloads/Products are found in the trash" +msgid "No %s found in Trash" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:65 +msgctxt "Context: Label for the featured images for a Download/Product" +msgid "%s Image" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:67 +msgctxt "Context: Text to set the featured image for a Download/Product" +msgid "Set %s Image" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:69 +msgctxt "Context: Text to remove the feature image for a Download/Product" +msgid "Remove %s Image" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:71 +msgctxt "Context: Text to use an image as the featured image for a Download/Produt" +msgid "Use as %s Image" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:73 +msgctxt "Context: Label for listing the attributes of a Download/Product" +msgid "%s Attributes" +msgstr "" + +#. translators: %s: Download plural label +#: includes/post-types.php:75 +msgctxt "Context: Label for filtering the Downloads/Products list" +msgid "Filter %s list" +msgstr "" + +#. translators: %s: Download plural label +#: includes/post-types.php:77 +msgctxt "Context: Label for the Downloads/Products list navigation" +msgid "%s list navigation" +msgstr "" + +#. translators: %s: Download plural label +#: includes/post-types.php:79 +msgctxt "Context: Lable for the Downloads/Products list" +msgid "%s list" +msgstr "" + +#: includes/post-types.php:127 +msgid "Download" +msgstr "" + +#: includes/post-types.php:128 +#: src/Admin/Menu/Header.php:142 +msgid "Downloads" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:179 +#: includes/post-types.php:188 +msgid "Enter %s name here" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:208 +msgctxt "taxonomy general name" +msgid "%s Categories" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:210 +msgctxt "taxonomy singular name" +msgid "%s Category" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:212 +msgid "Search %s Categories" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:214 +msgid "All %s Categories" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:216 +msgid "Parent %s Category" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:218 +msgid "Parent %s Category:" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:220 +msgid "Edit %s Category" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:222 +msgid "Update %s Category" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:224 +msgid "Add New %s Category" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:226 +msgid "New %s Category Name" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:260 +msgctxt "taxonomy general name" +msgid "%s Tags" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:262 +msgctxt "taxonomy singular name" +msgid "%s Tag" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:264 +msgctxt "singular: Download singular label" +msgid "Search %s Tags" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:266 +msgid "All %s Tags" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:268 +msgid "Parent %s Tag" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:270 +msgid "Parent %s Tag:" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:272 +msgid "Edit %s Tag" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:274 +msgid "Update %s Tag" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:276 +msgid "Add New %s Tag" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:278 +msgid "New %s Tag Name" +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:282 +msgid "Choose from most used %s tags" +msgstr "" + +#. translators: 1: Download singular label, 2: Opening anchor tag (do not translate), 3: Closing anchor tag (do not translate) +#: includes/post-types.php:385 +#: includes/post-types.php:387 +msgid "%1$s updated. %2$sView %1$s%3$s." +msgstr "" + +#. translators: 1: Download singular label, 2: Opening anchor tag (do not translate), 3: Closing anchor tag (do not translate) +#: includes/post-types.php:389 +msgid "%1$s published. %2$sView %1$s%3$s." +msgstr "" + +#. translators: 1: Download singular label, 2: Opening anchor tag (do not translate), 3: Closing anchor tag (do not translate) +#: includes/post-types.php:391 +msgid "%1$s saved. %2$sView %1$s%3$s." +msgstr "" + +#. translators: 1: Download singular label, 2: Opening anchor tag (do not translate), 3: Closing anchor tag (do not translate) +#: includes/post-types.php:393 +msgid "%1$s submitted. %2$sView %1$s%3$s." +msgstr "" + +#. translators: 1: Number of downloads updated, 2: Downloads plural label +#: includes/post-types.php:418 +msgctxt "Context: Admin notice for multiple Downloads/Products updated in bulk" +msgid "%1$d %2$s updated." +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:420 +msgctxt "Context: Admin notice for one Downloads/Products updated in bulk" +msgid "1 %s updated." +msgstr "" + +#. translators: 1: Number of downloads locked, 2: Downloads plural label +#: includes/post-types.php:423 +msgctxt "Context: Admin notice for multiple Downloads/Products not updated in bulk because they are being edited" +msgid "%1$d %2$s not updated, somebody is editing them." +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:425 +msgctxt "Context: Admin notice for one Downloads/Products not updated in bulk because it is being edited" +msgid "1 %s not updated, somebody is editing it." +msgstr "" + +#. translators: 1: Number of downloads deleted, 2: Downloads plural label +#: includes/post-types.php:428 +msgctxt "Context: Admin notice for multiple Downloads/Products permanently deleted" +msgid "%1$d %2$s permanently deleted." +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:430 +msgctxt "Context: Admin notice for one Downloads/Products permanently deleted" +msgid "1 %s permanently deleted." +msgstr "" + +#. translators: 1: Number of downloads moved to the Trash, 2: Downloads plural label +#: includes/post-types.php:433 +msgctxt "Context: Admin notice for multiple Downloads/Products moved to the Trash" +msgid "%1$d %2$s moved to the Trash." +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:435 +msgctxt "Context: Admin notice for one Downloads/Products moved to the Trash" +msgid "1 %s moved to the Trash." +msgstr "" + +#. translators: 1: Number of downloads restored from the Trash, 2: Downloads plural label +#: includes/post-types.php:438 +msgctxt "Context: Admin notice for multiple Downloads/Products restored from the Trash" +msgid "%1$d %2$s restored from the Trash." +msgstr "" + +#. translators: %s: Download singular label +#: includes/post-types.php:440 +msgctxt "Context: Admin notice for one Downloads/Products restored from the Trash" +msgid "1 %s restored from the Trash." +msgstr "" + +#: includes/post-types.php:477 +msgid "Checkout Page" +msgstr "" + +#: includes/post-types.php:481 +#: src/Admin/Settings/Tabs/General.php:135 +msgid "Success Page" +msgstr "" + +#: includes/post-types.php:485 +#: src/Admin/Settings/Tabs/General.php:144 +msgid "Failed Transaction Page" +msgstr "" + +#: includes/post-types.php:489 +#: src/Admin/Settings/Tabs/General.php:153 +msgid "Purchase History Page" +msgstr "" + +#: includes/post-types.php:493 +#: src/Admin/Settings/Tabs/General.php:162 +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 "" + +#. translators: %d is the customer ID. +#: includes/privacy-functions.php:255 +msgid "No customer with ID %d" +msgstr "" + +#: includes/privacy-functions.php:307 +msgid "Customer could not be anonymized due to payments that could not be anonymized or deleted." +msgstr "" + +#: includes/privacy-functions.php:352 +msgid "Anonymized Customer" +msgstr "" + +#: includes/privacy-functions.php:373 +msgid "Customer anonymized successfully" +msgstr "" + +#. translators: %d is the customer ID. +#: includes/privacy-functions.php:378 +msgid "Customer ID %d successfully anonymized." +msgstr "" + +#. translators: %d is the order ID. +#: includes/privacy-functions.php:403 +msgid "No order with ID %d." +msgstr "" + +#. translators: %d is the order status. +#: includes/privacy-functions.php:441 +msgid "Order not modified, due to status: %s." +msgstr "" + +#. translators: %1$d is the order ID, %2$s is the order status. +#: includes/privacy-functions.php:451 +msgid "Order %1$d with status %2$s deleted." +msgstr "" + +#. translators: %d is the order ID. +#: includes/privacy-functions.php:490 +msgid "Order ID %d successfully anonymized." +msgstr "" + +#: includes/privacy-functions.php:580 +#: includes/privacy-functions.php:627 +#: includes/privacy-functions.php:1198 +msgid "Customer Record" +msgstr "" + +#: includes/privacy-functions.php:585 +msgid "Billing Information" +msgstr "" + +#: includes/privacy-functions.php:596 +#: includes/privacy-functions.php:1017 +#: includes/privacy-functions.php:1213 +msgid "API Access Logs" +msgstr "" + +#: includes/privacy-functions.php:635 +msgid "Primary Email" +msgstr "" + +#: includes/privacy-functions.php:647 +msgid "All Email Addresses" +msgstr "" + +#: includes/privacy-functions.php:657 +msgid "Agreed to Terms" +msgstr "" + +#: includes/privacy-functions.php:667 +msgid "Agreed to Privacy Policy" +msgstr "" + +#: includes/privacy-functions.php:796 +msgid "Order ID / Number" +msgstr "" + +#: includes/privacy-functions.php:800 +msgid "Order Date" +msgstr "" + +#: includes/privacy-functions.php:804 +msgid "Order Completed Date" +msgstr "" + +#: includes/privacy-functions.php:810 +msgid "Order Total" +msgstr "" + +#: includes/privacy-functions.php:814 +msgid "Order Items" +msgstr "" + +#: includes/privacy-functions.php:905 +msgid "Date of Download" +msgstr "" + +#: includes/privacy-functions.php:909 +msgid "Product Downloaded" +msgstr "" + +#: includes/privacy-functions.php:939 +#: includes/privacy-functions.php:1208 +msgid "File Download Logs" +msgstr "" + +#: includes/privacy-functions.php:995 +msgid "Request" +msgstr "" + +#: includes/privacy-functions.php:1082 +msgid "Possibly Delete Customer" +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1150 +msgid "Customer for %s not deleted, due to remaining payments." +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1165 +msgid "Customer for %s successfully deleted." +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1177 +msgid "Customer for %s failed to be deleted." +msgstr "" + +#: includes/privacy-functions.php:1203 +msgid "Order Record" +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1248 +msgid "Customer for %s has been anonymized." +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1273 +msgid "No orders found for %s." +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1275 +msgid "All eligible orders anonymized or deleted for %s." +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1337 +msgid "All eligible file download logs anonymized or deleted for %s." +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1387 +msgid "No User found for %s, no access logs to remove." +msgstr "" + +#. translators: %s: email address +#: includes/privacy-functions.php:1405 +msgid "All API access logs deleted for %s." +msgstr "" + +#: includes/process-download.php:146 +msgid "Error 104: Sorry, this file could not be downloaded." +msgstr "" + +#: includes/process-download.php:146 +#: includes/process-download.php:193 +msgid "Error Downloading File" +msgstr "" + +#: includes/process-download.php:159 +msgid "Error 103: Error downloading file. Please contact support." +msgstr "" + +#: includes/process-download.php:159 +msgid "File download error" +msgstr "" + +#: includes/process-download.php:193 +msgid "Sorry, this file could not be downloaded." +msgstr "" + +#: includes/process-download.php:306 +msgid "You do not have permission to download this file" +msgstr "" + +#: includes/process-download.php:307 +msgid "Purchase Verification Failed" +msgstr "" + +#: includes/process-download.php:1102 +msgid "Invalid file" +msgstr "" + +#: includes/process-download.php:1109 +msgid "The requested file could not be found. Error 404." +msgstr "" + +#: includes/process-download.php:1110 +msgid "File not found" +msgstr "" + +#. translators: %s: email address +#: includes/process-purchase.php:144 +#: src/Checkout/Errors.php:182 +msgid "The email address %s is already in use." +msgstr "" + +#: includes/process-purchase.php:161 +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:167 +msgid "Error processing login. Nonce failed." +msgstr "" + +#: includes/process-purchase.php:286 +msgid "The selected payment gateway is not enabled" +msgstr "" + +#: includes/process-purchase.php:343 +msgid "One or more of the discounts you entered is invalid" +msgstr "" + +#: includes/process-purchase.php:360 +msgid "You must agree to the terms of use" +msgstr "" + +#: includes/process-purchase.php:374 +msgid "You must agree to the privacy policy" +msgstr "" + +#: includes/process-purchase.php:395 +msgid "Please enter your first name" +msgstr "" + +#: includes/process-purchase.php:413 +msgid "Please enter your billing city" +msgstr "" + +#: includes/process-purchase.php:425 +msgid "Please enter billing state / region" +msgstr "" + +#: includes/process-purchase.php:484 +#: includes/process-purchase.php:594 +#: includes/process-purchase.php:720 +#: includes/users/register.php:81 +msgid "Invalid email" +msgstr "" + +#: includes/process-purchase.php:568 +msgid "Username already exists" +msgstr "" + +#: includes/process-purchase.php:573 +msgid "Invalid username. Only lowercase letters (a-z) and numbers are allowed" +msgstr "" + +#: includes/process-purchase.php:574 +#: includes/users/register.php:59 +#: includes/users/register.php:67 +msgid "Invalid username" +msgstr "" + +#: includes/process-purchase.php:584 +msgid "You must register or login to complete your purchase" +msgstr "" + +#: includes/process-purchase.php:598 +msgid "You cannot use that email address to signup at this time." +msgstr "" + +#: includes/process-purchase.php:608 +#: src/Checkout/Errors.php:116 +msgid "Email already used. Login or use a different email to complete your purchase." +msgstr "" + +#: includes/process-purchase.php:620 +#: includes/process-purchase.php:733 +msgid "Enter an email" +msgstr "" + +#: includes/process-purchase.php:630 +msgid "Passwords do not match" +msgstr "" + +#: includes/process-purchase.php:641 +msgid "Enter a password" +msgstr "" + +#: includes/process-purchase.php:643 +msgid "Confirm your password" +msgstr "" + +#: includes/process-purchase.php:670 +msgid "You must log in or register to complete your purchase" +msgstr "" + +#: includes/process-purchase.php:707 +msgid "You must be logged into an account to purchase" +msgstr "" + +#: includes/process-purchase.php:724 +msgid "You cannot use that email address at this time." +msgstr "" + +#: includes/process-purchase.php:950 +msgid "The zip / postal code you entered for your billing address is invalid" +msgstr "" + +#: includes/process-purchase.php:1203 +msgid "An internal error has occurred, please try again or contact support." +msgstr "" + +#: includes/process-purchase.php:1221 +msgid "Your email address must be shorter than 100 characters." +msgstr "" + +#: includes/query-filters.php:51 +msgid "You do not have permission to view this file." +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:209 +msgid "All time" +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:81 +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 "Gross" +msgstr "" + +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:169 +msgid "Global Rate" +msgstr "" + +#: includes/reports/reports-functions.php:340 +#: includes/reports/reports-functions.php:1523 +msgid "Exclude Taxes" +msgstr "" + +#: includes/reports/reports-functions.php:344 +msgid "Gateways" +msgstr "" + +#: includes/reports/reports-functions.php:352 +msgid "Regions" +msgstr "" + +#: includes/reports/reports-functions.php:356 +msgid "Countries" +msgstr "" + +#: includes/reports/reports-functions.php:360 +msgid "Currencies" +msgstr "" + +#: includes/reports/reports-functions.php:364 +#: src/Admin/Settings/Tabs/Privacy.php:113 +msgid "Order Statuses" +msgstr "" + +#: includes/reports/reports-functions.php:535 +msgid "Custom" +msgstr "" + +#: includes/reports/reports-functions.php:540 +msgid "Last 30 Days" +msgstr "" + +#: includes/reports/reports-functions.php:541 +msgid "Month to Date" +msgstr "" + +#: includes/reports/reports-functions.php:543 +msgid "Quarter to Date" +msgstr "" + +#: includes/reports/reports-functions.php:545 +msgid "Year to Date" +msgstr "" + +#: includes/reports/reports-functions.php:607 +msgid "Previous period" +msgstr "" + +#: includes/reports/reports-functions.php:608 +msgid "Previous month" +msgstr "" + +#: includes/reports/reports-functions.php:609 +msgid "Previous quarter" +msgstr "" + +#: includes/reports/reports-functions.php:610 +msgid "Previous year" +msgstr "" + +#. translators: %s: Timezone ID +#: includes/reports/reports-functions.php:1325 +msgid "Chart time zone: %s" +msgstr "" + +#: includes/reports/reports-functions.php:1429 +msgid "compared to" +msgstr "" + +#. translators: %s: Default currency of the store, in standard 3 character format (example: USD or EUR) +#: includes/reports/reports-functions.php:1695 +msgid "%s - Converted" +msgstr "" + +#: includes/reports/reports-functions.php:1742 +msgid "All order statuses" +msgstr "" + +#: includes/reports/reports-functions.php:1868 +msgid "Try the Sales/Earnings iOS App!" +msgstr "" + +#: includes/shortcodes.php:285 +msgid "Purchase All Items" +msgstr "" + +#. translators: plural download label +#: includes/shortcodes.php:617 +msgctxt "download post type name" +msgid "No %s found" +msgstr "" + +#: includes/shortcodes.php:712 +msgid "You must be logged in to view this payment receipt." +msgstr "" + +#: includes/shortcodes.php:724 +msgid "Receipt could not be retrieved, your purchase session has expired." +msgstr "" + +#: includes/shortcodes.php:808 +msgid "Your profile could not be updated. Please contact a site administrator." +msgstr "" + +#: includes/shortcodes.php:844 +msgid "The passwords you entered do not match. Please try again." +msgstr "" + +#: includes/shortcodes.php:855 +msgid "The email you entered is invalid. Please enter a valid email." +msgstr "" + +#: includes/shortcodes.php:866 +#: includes/users/register.php:77 +msgid "This email address is not available." +msgstr "" + +#: includes/shortcodes.php:1001 +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:93 +#: src/Admin/Settings/Tabs/Misc.php:150 +msgctxt "text shown on the Add to Cart Button when the product is already in the cart" +msgid "Checkout" +msgstr "" + +#: includes/template-functions.php:199 +#: src/Assets/Localization.php:62 +msgid "Loading" +msgstr "" + +#: includes/template-functions.php:213 +msgid "Added to cart" +msgstr "" + +#. translators: the formatted tax rate +#: includes/template-functions.php:220 +msgid "Includes %1$s tax" +msgstr "" + +#. translators: the formatted tax rate +#: includes/template-functions.php:223 +msgid "Excluding %1$s tax" +msgstr "" + +#: includes/template-functions.php:478 +msgid "White" +msgstr "" + +#: includes/template-functions.php:482 +msgid "Gray" +msgstr "" + +#: includes/template-functions.php:486 +msgid "Blue" +msgstr "" + +#: includes/template-functions.php:490 +msgid "Red" +msgstr "" + +#: includes/template-functions.php:494 +msgid "Green" +msgstr "" + +#: includes/template-functions.php:498 +msgid "Yellow" +msgstr "" + +#: includes/template-functions.php:502 +msgid "Orange" +msgstr "" + +#: includes/template-functions.php:506 +msgid "Dark Gray" +msgstr "" + +#: includes/template-functions.php:510 +msgid "Inherit" +msgstr "" + +#: includes/template-functions.php:529 +msgid "Plain Text" +msgstr "" + +#: includes/upgrades/functions.php:144 +msgid "Tax Rates" +msgstr "" + +#: includes/upgrades/functions.php:160 +msgid "Customer Email Addresses" +msgstr "" + +#: includes/upgrades/functions.php:164 +msgid "Customer Notes" +msgstr "" + +#: includes/upgrades/functions.php:172 +msgid "Order Notes" +msgstr "" + +#. translators: %s: URL to request a new verification link +#: includes/user-functions.php:840 +msgid "Sorry but your account verification link has expired. Click here to request a new verification URL." +msgstr "" + +#: includes/user-functions.php:872 +msgid "You must be logged in to verify your account." +msgstr "" + +#: includes/user-functions.php:876 +msgid "Your account has already been verified." +msgstr "" + +#: includes/user-functions.php:917 +msgid "Invalid verification token provided." +msgstr "" + +#: includes/user-functions.php:1043 +msgid "EDD API Keys" +msgstr "" + +#: includes/user-functions.php:1050 +msgid "Generate API Key" +msgstr "" + +#: includes/user-functions.php:1055 +msgid "Public Key:" +msgstr "" + +#: includes/user-functions.php:1058 +msgid "Secret Key:" +msgstr "" + +#: includes/user-functions.php:1061 +msgid "Token:" +msgstr "" + +#: includes/user-functions.php:1067 +msgid "Revoke API Keys" +msgstr "" + +#: includes/user-functions.php:1088 +msgid "Last Used:" +msgstr "" + +#. translators: %s: iOS App URL +#: includes/user-functions.php:1107 +msgid "Easy Digital Downloads iOS App" +msgstr "" + +#: includes/user-functions.php:1115 +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:152 +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:127 +msgid "Your request could not be completed." +msgstr "" + +#: includes/users/lost-password.php:134 +msgid "You did it! Check your email for instructions on resetting your password." +msgstr "" + +#: includes/users/lost-password.php:230 +msgid "Your password reset link has expired. Please request a new link below." +msgstr "" + +#: includes/users/lost-password.php:252 +#: includes/users/lost-password.php:257 +#: includes/users/lost-password.php:262 +#: includes/users/lost-password.php:268 +msgid "Invalid password reset request." +msgstr "" + +#: includes/users/lost-password.php:277 +#: includes/users/register.php:98 +msgid "The password cannot be a space or all spaces." +msgstr "" + +#: includes/users/lost-password.php:282 +#: includes/users/register.php:103 +msgid "The passwords do not match." +msgstr "" + +#: includes/users/lost-password.php:287 +#: includes/users/lost-password.php:292 +msgid "Your password could not be reset." +msgstr "" + +#: includes/users/lost-password.php:301 +msgid "Your password was successfully reset." +msgstr "" + +#: includes/users/register.php:63 +msgid "Username already taken" +msgstr "" + +#: includes/users/register.php:85 +msgid "Invalid payment email" +msgstr "" + +#: includes/users/register.php:89 +msgid "Registration form validation failed." +msgstr "" + +#: includes/users/register.php:125 +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:389 +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 "" + +#. translators: %s: Download singular label +#: includes/widgets.php:244 +#: includes/widgets.php:366 +#: src/Admin/Downloads/Metaboxes/Details.php:53 +msgid "%s Details" +msgstr "" + +#. translators: %s: Download singular label +#: includes/widgets.php:247 +msgid "Display the details of a specific %s" +msgstr "" + +#: includes/widgets.php:394 +msgid "Display Type:" +msgstr "" + +#: includes/widgets.php:395 +msgid "Current" +msgstr "" + +#: includes/widgets.php:396 +msgid "Specific" +msgstr "" + +#. translators: %s: Download singular label +#: includes/widgets.php:403 +msgid "%s:" +msgstr "" + +#. translators: %s: Download singular label +#: includes/widgets.php:422 +msgid "%s ID" +msgstr "" + +#. translators: %s: Download singular label +#: includes/widgets.php:430 +msgid "Show %s Title" +msgstr "" + +#: includes/widgets.php:436 +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:163 +msgid "Show Purchase Button" +msgstr "" + +#. translators: %s: download category label +#: includes/widgets.php:444 +msgctxt "plural: Download category label (example: Show Download Categories)" +msgid "Show %s" +msgstr "" + +#. translators: %s: download tag label +#: includes/widgets.php:452 +msgctxt "plural: Download tag label (example: Show Download Tags)" +msgid "Show %s" +msgstr "" + +#: src/Admin/Assets/Localization.php:66 +msgid "Add New Download" +msgstr "" + +#: src/Admin/Assets/Localization.php:67 +msgid "Use This File" +msgstr "" + +#: src/Admin/Assets/Localization.php:68 +msgid "Sorry, not available for variable priced products." +msgstr "" + +#: src/Admin/Assets/Localization.php:69 +msgid "Are you sure you want to delete this item?" +msgstr "" + +#: src/Admin/Assets/Localization.php:70 +msgid "Are you sure you want to delete this adjustment?" +msgstr "" + +#: src/Admin/Assets/Localization.php:71 +msgid "Are you sure you want to delete this note?" +msgstr "" + +#: src/Admin/Assets/Localization.php:72 +msgid "Are you sure you want to delete this tax rate?" +msgstr "" + +#: src/Admin/Assets/Localization.php:73 +msgid "Are you sure you want to revoke this API key?" +msgstr "" + +#: src/Admin/Assets/Localization.php:74 +msgid "Are you sure you want to regenerate this API key?" +msgstr "" + +#: src/Admin/Assets/Localization.php:75 +msgid "Are you sure you want to resend the purchase receipt?" +msgstr "" + +#: src/Admin/Assets/Localization.php:76 +msgid "Are you sure you want to disconnect the WordPress user from this customer record?" +msgstr "" + +#: src/Admin/Assets/Localization.php:77 +msgid "Copy these links to your clipboard and give them to your customer" +msgstr "" + +#. translators: %s: Download singular label +#: src/Admin/Assets/Localization.php:79 +msgid "Are you sure you want to delete this %s?" +msgstr "" + +#. translators: %s: Downloads plural label +#: src/Admin/Assets/Localization.php:81 +msgid "Type to search %s" +msgstr "" + +#. translators: %s: Download singular label +#: src/Admin/Assets/Localization.php:83 +#: src/HTML/ProductSelect.php:80 +msgid "Choose a %s" +msgstr "" + +#. translators: %s: Downloads plural label +#: src/Admin/Assets/Localization.php:85 +msgid "Choose one or more %s" +msgstr "" + +#: src/Admin/Assets/Localization.php:86 +msgid "You must have at least one price" +msgstr "" + +#: src/Admin/Assets/Localization.php:87 +msgid "You must have at least one field" +msgstr "" + +#: src/Admin/Assets/Localization.php:88 +msgid "Payments must contain at least one item" +msgstr "" + +#: src/Admin/Assets/Localization.php:89 +msgid "No match for:" +msgstr "" + +#: src/Admin/Assets/Localization.php:90 +msgid "Item price must be numeric" +msgstr "" + +#: src/Admin/Assets/Localization.php:91 +msgid "Item tax must be numeric" +msgstr "" + +#: src/Admin/Assets/Localization.php:92 +msgid "Quantity must be numeric" +msgstr "" + +#: src/Admin/Assets/Localization.php:94 +msgid "You must choose a method." +msgstr "" + +#: src/Admin/Assets/Localization.php:95 +msgid "Required fields not completed." +msgstr "" + +#: src/Admin/Assets/Localization.php:96 +msgid "Are you sure you want to reset your store? This process is not reversible. Please be sure you have a recent backup." +msgstr "" + +#: src/Admin/Assets/Localization.php:97 +msgid "We are sorry but your browser is not compatible with this kind of file upload. Please upgrade your browser." +msgstr "" + +#: src/Admin/Assets/Localization.php:99 +msgid "Hide advanced settings" +msgstr "" + +#: src/Admin/Assets/Localization.php:100 +msgid "There are no downloads attached to this payment" +msgstr "" + +#: src/Admin/Assets/Localization.php:101 +msgid "Please wait …" +msgstr "" + +#: src/Admin/Assets/Localization.php:102 +msgid "You must save your changes to send the test email." +msgstr "" + +#: src/Admin/Assets/Localization.php:103 +msgid "Either Letters or Numbers should be selected." +msgstr "" + +#: src/Admin/Assets/Localization.php:104 +msgid "Deleting a price ID with existing orders can cause issues with your store. Are you sure you want to delete this?" +msgstr "" + +#: src/Admin/Assets/Localization.php:107 +msgid "Confirm" +msgstr "" + +#: src/Admin/Assets/Localization.php:109 +msgid "Copied" +msgstr "" + +#: src/Admin/Assets/Localization.php:115 +msgid "The edd_use_35_media_ui filter is no longer supported." +msgstr "" + +#: src/Admin/Assets/Localization.php:124 +#: src/Admin/Settings/Tabs/General.php:112 +msgid "Select a region" +msgstr "" + +#: src/Admin/Discounts/Generate.php:38 +msgid "Generate Code" +msgstr "" + +#: src/Admin/Discounts/Generate.php:72 +msgid "Unlock with Pro" +msgstr "" + +#: src/Admin/Discounts/Generate.php:73 +msgid "Upgrade to Easy Digital Downloads (Pro) to easily generate unique discount codes, and more." +msgstr "" + +#: src/Admin/Downloads/Editor/Details.php:169 +msgid "Additional Details" +msgstr "" + +#. translators: %s: Download singular label +#: src/Admin/Downloads/Editor/Details.php:182 +msgid "Create price variations for this %s." +msgstr "" + +#: src/Admin/Downloads/Editor/Details.php:200 +msgid "Allow purchasing multiple variations in the same order." +msgstr "" + +#: src/Admin/Downloads/Editor/Sections.php:165 +#: src/Admin/Sections.php:302 +msgid "Invalid section" +msgstr "" + +#: src/Admin/Downloads/Editor/VariablePrices.php:63 +msgid "Prices" +msgstr "" + +#: src/Admin/Downloads/Editor/VariablePrices.php:83 +msgid "Expand All" +msgstr "" + +#: src/Admin/Downloads/Editor/VariablePrices.php:87 +msgid "Collapse All" +msgstr "" + +#: src/Admin/Downloads/Editor/VariablePrices.php:100 +msgid "Add Variation" +msgstr "" + +#: src/Admin/Downloads/Editor/VariablePrices.php:160 +#: src/Admin/Downloads/Editor/VariablePrices.php:168 +msgid "Variation Name" +msgstr "" + +#. translators: %d: price ID +#: src/Admin/Downloads/Editor/VariablePrices.php:225 +msgid "Move price %d up" +msgstr "" + +#. translators: %d: price ID +#: src/Admin/Downloads/Editor/VariablePrices.php:235 +msgid "Move price %d down" +msgstr "" + +#. translators: %s: Download singular label +#: src/Admin/Downloads/Metaboxes/Stats.php:51 +msgid "%s Stats" +msgstr "" + +#: src/Admin/Emails/ListTable.php:76 +msgid "Sender" +msgstr "" + +#: src/Admin/Emails/ListTable.php:77 +msgid "Context" +msgstr "" + +#: src/Admin/Emails/ListTable.php:78 +msgid "Recipient" +msgstr "" + +#: src/Admin/Emails/ListTable.php:80 +msgid "Updated" +msgstr "" + +#: src/Admin/Emails/ListTable.php:142 +msgid "Disable Email" +msgstr "" + +#: src/Admin/Emails/ListTable.php:146 +msgid "Enable Email" +msgstr "" + +#: src/Admin/Emails/ListTable.php:217 +msgid "No emails found matching filters." +msgstr "" + +#: src/Admin/Emails/ListTable.php:314 +msgid "Add New Email" +msgstr "" + +#: src/Admin/Emails/ListTable.php:425 +msgid "Filter by status" +msgstr "" + +#: src/Admin/Emails/ListTable.php:429 +msgid "All Emails" +msgstr "" + +#: src/Admin/Emails/ListTable.php:432 +msgid "Enabled Emails" +msgstr "" + +#: src/Admin/Emails/ListTable.php:435 +msgid "Disabled Emails" +msgstr "" + +#: src/Admin/Emails/ListTable.php:450 +msgid "Filter by sender" +msgstr "" + +#: src/Admin/Emails/ListTable.php:454 +msgid "All Senders" +msgstr "" + +#: src/Admin/Emails/ListTable.php:474 +msgid "Filter by recipient" +msgstr "" + +#: src/Admin/Emails/ListTable.php:478 +msgid "All Recipients" +msgstr "" + +#: src/Admin/Emails/ListTable.php:498 +msgid "Filter by context" +msgstr "" + +#: src/Admin/Emails/ListTable.php:502 +msgid "All Contexts" +msgstr "" + +#: src/Admin/Emails/LogsTable.php:72 +msgid "To" +msgstr "" + +#: src/Admin/Emails/LogsTable.php:73 +msgid "Email Object" +msgstr "" + +#: src/Admin/Emails/LogsTable.php:74 +msgid "Date Sent" +msgstr "" + +#: src/Admin/Emails/LogsTable.php:166 +msgid "Search Logs" +msgstr "" + +#. translators: %s: Order number +#: src/Admin/Emails/LogsTable.php:208 +msgid "Order %s" +msgstr "" + +#. translators: %s: User display name +#: src/Admin/Emails/LogsTable.php:228 +msgid "User %s" +msgstr "" + +#. translators: %s: Refund number +#: src/Admin/Emails/LogsTable.php:249 +msgid "Refund %s" +msgstr "" + +#: src/Admin/Emails/Manager.php:54 +#: src/Admin/Emails/Manager.php:87 +#: src/Admin/Emails/Manager.php:211 +#: src/Admin/Emails/Screen.php:98 +msgid "Missing email ID." +msgstr "" + +#: src/Admin/Emails/Manager.php:92 +msgid "Invalid nonce." +msgstr "" + +#: src/Admin/Emails/Manager.php:109 +msgid "This email status cannot be changed." +msgstr "" + +#: src/Admin/Emails/Manager.php:117 +msgid "This email status could not be changed." +msgstr "" + +#. translators: 1: opening strong tag, 2: closing string tag, 3: required tag +#: src/Admin/Emails/Manager.php:155 +msgid "%1$sImportant:%2$s The %3$s template tag must remain in this email. Do not delete it." +msgstr "" + +#: src/Admin/Emails/Manager.php:215 +#: src/Admin/Emails/Screen.php:105 +msgid "Invalid email ID." +msgstr "" + +#: src/Admin/Emails/Messages.php:82 +msgid "Your email could not be saved because it is missing required content." +msgstr "" + +#: src/Admin/Emails/Messages.php:87 +msgid "The test email was sent successfully." +msgstr "" + +#: src/Admin/Emails/Messages.php:91 +msgid "The test email could not be sent. Please check your email settings and try again." +msgstr "" + +#: src/Admin/Emails/Messages.php:96 +msgid "The email was successfully added." +msgstr "" + +#: src/Admin/Emails/Messages.php:100 +msgid "The email could not be added." +msgstr "" + +#: src/Admin/Emails/Messages.php:105 +msgid "The email was successfully deleted." +msgstr "" + +#: src/Admin/Emails/Messages.php:109 +msgid "The email could not be deleted." +msgstr "" + +#: src/Admin/Emails/Reset.php:49 +msgid "Restore Default Email Content?" +msgstr "" + +#: src/Admin/Emails/Reset.php:50 +msgid "Restoring the default content will remove any customizations that you have made to the current email content. Do you want to continue?" +msgstr "" + +#: src/Admin/Emails/Reset.php:53 +msgid "Confirm Restore" +msgstr "" + +#: src/Admin/Emails/Screen.php:36 +msgid "Email Reports" +msgstr "" + +#: src/Admin/Extensions/Card.php:181 +#: src/Admin/Promos/Notices/Emails.php:133 +#: src/Gateways/Stripe/Admin/LicenseManager.php:263 +#: src/Gateways/Stripe/Admin/LicenseManager.php:289 +#: src/Gateways/Stripe/Admin/LicenseManager.php:311 +#: src/Gateways/Stripe/Admin/LicenseManager.php:338 +msgid "Learn More" +msgstr "" + +#: src/Admin/Extensions/Card.php:367 +msgid "Recommended" +msgstr "" + +#. translators: the plugin version +#: src/Admin/Extensions/Card.php:386 +msgid "Version: %s" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:273 +msgid "Log In to Your Account to Download %s" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:281 +msgid "Upgrade Today to Access %s!" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:297 +msgid "Activate %s" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:301 +msgid "Deactivate %s" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:349 +msgid "Configure %s" +msgstr "" + +#. translators: the plural Downloads label. +#: src/Admin/Extensions/Extension.php:362 +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: pass name, 2: opening anchor tag, 3: closing anchor tag. +#: src/Admin/Extensions/ExtensionPage.php:162 +msgid "Using the 1-Click Installation feature requires Easy Digital Downloads (Pro), which you have access to with your %1$s. %2$sInstall (Pro) now%3$s." +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag. +#: src/Admin/Extensions/ExtensionPage.php:180 +msgid "Missing access to an extension? %1$sAdd your license key now%2$s." +msgstr "" + +#: src/Admin/Extensions/ExtensionPage.php:214 +msgid "EDD (Pro) Required" +msgstr "" + +#: src/Admin/Extensions/ExtensionPage.php:218 +msgid "Upgrade Now" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:89 +#: src/Admin/PassHandler/Settings.php:128 +#: src/Licensing/Settings.php:57 +msgid "Activating" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:90 +msgid "Installing" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:91 +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:94 +#: src/Admin/Extensions/Extension_Manager.php:191 +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:99 +msgid "extensions found" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:100 +#: src/Admin/PassHandler/Settings.php:129 +#: src/Licensing/Settings.php:58 +msgid "Deactivating" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:160 +#: src/Admin/Extensions/Extension_Manager.php:273 +#: src/Admin/Extensions/Extension_Manager.php:353 +msgid "There was an error while performing your request." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:175 +msgid "Plugin installation is not available for you on this site." +msgstr "" + +#. translators: 1: opening anchor tag, do not translate, 2: closing anchor tag, do not translate. +#: src/Admin/Extensions/Extension_Manager.php:185 +msgid "Could not install the plugin. Please %1$sdownload%2$s and install it manually via Plugins > Add New." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:257 +msgid "Plugin installed." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:257 +msgid "Extension installed." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:279 +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:296 +msgid "Could not activate the %s." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:319 +msgid "Plugin activated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:319 +msgid "Extension activated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:321 +#: 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:321 +msgid "Extension installed & activated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:330 +msgid "Activated" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:359 +msgid "Plugin deactivation is not available for you on this site." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:378 +msgid "Plugin deactivated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:378 +msgid "Extension deactivated." +msgstr "" + +#: src/Admin/Extensions/Legacy.php:69 +msgid "Inactive — Part of EDD" +msgstr "" + +#: src/Admin/Extensions/Legacy.php:115 +msgid "Auto Register has been merged into Easy Digital Downloads. It has been deactivated and you can safely delete the Auto Register plugin. Please review your new user emails to ensure that any customizations were retained during the migration." +msgstr "" + +#: src/Admin/Extensions/Legacy.php:118 +msgid "View Emails" +msgstr "" + +#. translators: %s: name of the extension. +#: src/Admin/Extensions/Legacy.php:187 +msgid "%s is now part of EDD!" +msgstr "" + +#. translators: %s: name of the extension. +#: src/Admin/Extensions/Legacy.php:192 +msgid "The functionality of %1$s has been merged into Easy Digital Downloads. It has been deactivated and you can safely delete the %2$s plugin." +msgstr "" + +#: src/Admin/Extensions/Legacy.php:211 +msgid "View Plugins" +msgstr "" + +#. translators: %s: Email tag that will be replaced with the customer name +#: src/Admin/Extensions/Legacy/AutoRegister.php:103 +#: src/Emails/Templates/OrderReceipt.php:105 +#: src/Emails/Templates/OrderRefund.php:129 +msgctxt "Context: This is an email tag (placeholder) that will be replaced at the time of sending the email" +msgid "Dear %s," +msgstr "" + +#: src/Admin/Extensions/Legacy/AutoRegister.php:104 +msgid "Below are your login details:" +msgstr "" + +#. translators: %s: Email tag that will be replaced with the customer username +#: src/Admin/Extensions/Legacy/AutoRegister.php:106 +msgid "Your Username: %s" +msgstr "" + +#. translators: %s: Site name email tag +#: src/Admin/Extensions/Legacy/AutoRegister.php:132 +msgid "[%s] Login Details" +msgstr "" + +#. translators: %s: Email tag that will be replaced with the Site Name +#: src/Admin/Extensions/Legacy/AutoRegister.php:146 +msgctxt "Used to insert the {sitename} email tag into default email content." +msgid "New user registration on your site %s:" +msgstr "" + +#. translators: %s: Username email tag +#: src/Admin/Extensions/Legacy/AutoRegister.php:148 +msgctxt "Used to insert the {username} email tag into default email content." +msgid "Username: %s" +msgstr "" + +#. translators: %s: User email email tag +#: src/Admin/Extensions/Legacy/AutoRegister.php:150 +msgctxt "Used to insert the {user_email} email tag into default email content." +msgid "Email: %s" +msgstr "" + +#. translators: %s: Email tag that will be replaced with the Site Name +#: src/Admin/Extensions/Legacy/AutoRegister.php:163 +#: src/Emails/Templates/NewUserAdmin.php:85 +msgctxt "Context: This is an email tag (placeholder) that will be replaced at the time of sending the email" +msgid "[%s] New User Registration" +msgstr "" + +#: src/Admin/Extensions/Traits/Emails.php:60 +msgid "Update Now" +msgstr "" + +#. translators: %1$s number of notifications; %2$s opening span tag; %3$s closing span tag +#: src/Admin/Menu/Header.php:82 +msgid "%1$s %2$sunread notifications%3$s" +msgstr "" + +#: src/Admin/Menu/Header.php:166 +#: src/Admin/PassHandler/Handler.php:131 +msgid "View Extensions" +msgstr "" + +#: src/Admin/Menu/Header.php:168 +msgid "Manage Extensions" +msgstr "" + +#: src/Admin/Menu/Pages.php:81 +msgid "EDD Settings" +msgstr "" + +#: src/Admin/Menu/Pages.php:87 +msgid "EDD Emails" +msgstr "" + +#: src/Admin/Menu/Pages.php:93 +msgid "EDD Tools" +msgstr "" + +#: src/Admin/Menu/Pages.php:109 +#: src/Admin/Menu/Pages.php:110 +msgid "EDD Upgrades" +msgstr "" + +#: src/Admin/Menu/Pages.php:158 +msgid "NEW!" +msgstr "" + +#: src/Admin/Menu/SecondaryNavigation.php:62 +msgid "Secondary menu" +msgstr "" + +#. translators: %s: Search query. +#: src/Admin/Menu/SecondaryNavigation.php:84 +msgid "Search results for: %s" +msgstr "" + +#: src/Admin/Notifications/GBLegacy.php:53 +msgid "Please Update Your Settings" +msgstr "" + +#: src/Admin/Notifications/GBLegacy.php:54 +msgid "We recently updated our list of regions for the United Kingdom. We have detected that your store is using an outdated region for the business settings or tax rates. Please review these settings and update them if needed." +msgstr "" + +#: src/Admin/Notifications/GBLegacy.php:111 +msgid "Update Business Region" +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:40 +#: src/Admin/Settings/Tabs/Emails.php:54 +msgid "Logo" +msgstr "" + +#: src/Admin/Onboarding/Steps/ConfigureEmails.php:60 +#: src/Admin/Settings/Tabs/Emails.php:60 +msgid "From Name" +msgstr "" + +#: src/Admin/Onboarding/Steps/ConfigureEmails.php:82 +#: src/Admin/Settings/Tabs/Emails.php:68 +msgid "From 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:38 +msgid "Product details" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:43 +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:51 +msgid "Product Name" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:63 +msgid "Set image" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:63 +msgid "Set Image" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:69 +msgid "Pricing Options" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:73 +msgid "Single price" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:74 +msgid "Variable price" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:79 +msgid "Product Price" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:120 +msgid "Add New Price" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:141 +msgid "Add your first file" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:150 +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:166 +msgid "Congratulations!" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:167 +msgid "You've set up your store and your first product has been created." +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:168 +msgid "Edit My Product" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:169 +msgid "Explore Extensions" +msgstr "" + +#. translators: the plugin name. +#: src/Admin/Onboarding/Steps/Tools.php:82 +msgid "%s is already active." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:88 +msgid "You already have a solution installed for this feature." +msgstr "" + +#. translators: %s The plugin name. +#: src/Admin/Onboarding/Steps/Tools.php:93 +msgid "Installs %s" +msgstr "" + +#. translators: %s The plugin name. +#: src/Admin/Onboarding/Steps/Tools.php:98 +msgid "Activates %s" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:111 +msgid "Based on your selection above, the following plugins will be installed and activated:" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:115 +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:120 +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:125 +msgid "Continue" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:131 +msgid "Plugins were successfully installed!" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:147 +msgid "Essential eCommerce Features" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:148 +msgid "Get all the essential eCommerce features to sell digital products with WordPress." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:159 +msgid "Reliable Email Delivery" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:160 +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:172 +msgid "Analytics Tools" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:173 +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:189 +msgid "SEO Tools" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:190 +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/Steps/Tools.php:204 +msgid "Conversion Tools" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:205 +msgid "Get the #1 conversion optimization plugin to convert your growing website traffic into subscribers, leads and sales." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:266 +#: src/Telemetry/Tracking.php:143 +#: src/Telemetry/Tracking.php:270 +msgid "Join the EDD Community" +msgstr "" + +#. translators: %1$s: the opening anchor tag, %2$s: the closing anchor tag +#: src/Admin/Onboarding/Steps/Tools.php:281 +msgid "Help us provide a better experience and faster fixes by sharing some %1$sanonymous data%2$s about how you use Easy Digital Downloads." +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:230 +msgid "Business" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:231 +msgid "Tell us a little bit about your business." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:232 +msgid "Where is your business located? This helps Easy Digital Downloads configure the checkout and receipt templates." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:236 +#: src/Gateways/Stripe/Admin/Settings.php:468 +msgid "Payment Methods" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:237 +msgid "Start accepting payments today!" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:243 +msgid "Configure your Receipts" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:244 +msgid "Customize the purchase receipt that your customers will receive." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:249 +msgid "Conversion and Optimization tools" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:250 +msgid "We have selected our recommended tools and features to help boost conversions and optimize your digital store." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:255 +msgid "What are you going to sell?" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:256 +msgid "Let's get started creating your first awesome product." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:276 +msgid "Let's get started with your next great product." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:303 +msgid "Unknown Onboarding Step." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:303 +msgid "Onboarding Wizard" +msgstr "" + +#. translators: %s: The company name of the person giving the testimonial +#: src/Admin/Onboarding/Wizard.php:424 +msgctxt "Context: The title/role of the person giving the testimonial. Example: Podcast Coach - How I Built It. The company name remains untranslated" +msgid "Podcast Coach - %s" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:431 +msgctxt "Context: Direct quote used as a testimonial for Easy Digital Downloads" +msgid "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." +msgstr "" + +#. translators: %s: The company name of the person giving the testimonial +#: src/Admin/Onboarding/Wizard.php:443 +msgctxt "Context: The title/role of the person giving the testimonial. Example: Flea Market Insiders. The company name remains untranslated" +msgid "Founder - %s" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:450 +msgctxt "Context: Direct quote used as a testimonial for Easy Digital Downloads" +msgid "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%." +msgstr "" + +#. translators: %s: The company name of the person giving the testimonial +#: src/Admin/Onboarding/Wizard.php:462 +msgctxt "Context: The title/role of the person giving the testimonial. Example: Community Leader - BobWP. The company name remains untranslated" +msgid "Community Leader & Podcaster - %s" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:469 +msgctxt "Context: Direct quote used as a testimonial for Easy Digital Downloads" +msgid "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." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:481 +msgid "Welcome, and thanks for choosing us!" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:482 +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:483 +msgid "Get Started" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:484 +msgid "Creators ❤️ Easy Digital Downloads" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:573 +msgid "Go Back" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:577 +msgid "Skip this step" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:578 +msgid "Save & Continue" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:583 +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 +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:65 +msgid "License Key" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:75 +msgid "Paste license key" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:97 +msgid "We see that you have an active pass--just hit Verify to get started with (Pro)." +msgstr "" + +#: src/Admin/PassHandler/Settings.php:127 +msgid "Verifying" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:130 +msgid "Just a moment while we connect your site and upgrade you to (Pro)." +msgstr "" + +#: src/Admin/PassHandler/Settings.php:154 +msgid "Easy Digital Downloads (Pro) Key" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:155 +msgid "Go Pro With Easy Digital Downloads" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:180 +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:195 +msgid "To unlock more features, consider %1$supgrading to Pro%2$s." +msgstr "" + +#: src/Admin/PassHandler/Settings.php:202 +msgid "As a valued EDD user you receive 50% off, automatically applied at checkout!" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:203 +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 "" + +#: src/Admin/Promos/About.php:99 +msgid "About Easy Digital Downloads" +msgstr "" + +#: src/Admin/Promos/About.php:100 +#: src/Admin/Promos/About.php:143 +msgid "About Us" +msgstr "" + +#: src/Admin/Promos/About.php:144 +msgid "Getting Started" +msgstr "" + +#: src/Admin/Promos/About.php:212 +msgid "Welcome to Easy Digital Downloads, the #1 WordPress eCommerce plugin for easily collecting payments. At Easy Digital Downloads, we build software that helps you sell your digital products and services in minutes." +msgstr "" + +#: src/Admin/Promos/About.php:215 +msgid "Over the years we've watched as many plugins and platforms have been created that say they focus on \"digital products\", but take huge fees out of each sale, charge for each product created, and even charge \"relisting fees\". We take a different approach, to create the most powerful digital eCommerce solution, with no hidden fees or monthly costs." +msgstr "" + +#: src/Admin/Promos/About.php:218 +msgid "Our goal is to remove the pain from running your own eCommerce store and make it effortless, so you can get back to doing what you do best." +msgstr "" + +#: src/Admin/Promos/About.php:221 +msgid "Since 2012, we've been building the best solution for selling digital products, services, and memberships with WordPress." +msgstr "" + +#: src/Admin/Promos/About.php:224 +msgid "Thanks for being the best part of Easy Digital Downloads!" +msgstr "" + +#: src/Admin/Promos/About.php:230 +msgid "The Awesome Motive Team photo" +msgstr "" + +#: src/Admin/Promos/About.php:232 +msgid "The Awesome Motive Team" +msgstr "" + +#. translators: %s: status label. +#: src/Admin/Promos/About.php:273 +msgctxt "The status of an installed WordPress plugin" +msgid "Status: %s" +msgstr "" + +#: src/Admin/Promos/About.php:317 +msgid "How to Get Started" +msgstr "" + +#: src/Admin/Promos/About.php:320 +#: src/Admin/Promos/About.php:334 +msgid "Welcome to Easy Digital Downloads" +msgstr "" + +#: src/Admin/Promos/About.php:323 +msgid "The best WordPress eCommerce plugin for selling digital downloads. Start selling eBooks, software, music, digital art, and more within minutes. Accept payments, manage subscriptions, advanced access control, and more." +msgstr "" + +#: src/Admin/Promos/About.php:327 +msgid "Launch Setup Wizard" +msgstr "" + +#: src/Admin/Promos/About.php:328 +msgid "Read the Setup Guide" +msgstr "" + +#: src/Admin/Promos/About.php:347 +msgid "Get Easy Digital Downloads (Pro) and Unlock your Growth Potential" +msgstr "" + +#: src/Admin/Promos/About.php:353 +msgid "Thanks for being a loyal Easy Digital Downloads user. Upgrade to Easy Digital Downloads (Pro) to unlock all the awesome features and experience why Easy Digital Downloads is regarded as the best eCommerce plugin for digital products and services." +msgstr "" + +#. translators: %s: stars. +#: src/Admin/Promos/About.php:366 +msgid "We know that you will truly love Easy Digital Downloads. It has over 450+ five star ratings (%s) and over 50,000+ professionals and creators use it to run their businesses and projects." +msgstr "" + +#: src/Admin/Promos/About.php:387 +msgid "Sell subscriptions for products and services" +msgstr "" + +#: src/Admin/Promos/About.php:391 +msgid "Remove additional transaction fees" +msgstr "" + +#: src/Admin/Promos/About.php:395 +msgid "Add customers to your email subscriber list" +msgstr "" + +#: src/Admin/Promos/About.php:399 +msgid "Give away lead magnets to grow your email list" +msgstr "" + +#: src/Admin/Promos/About.php:403 +msgid "Build advanced product bundles" +msgstr "" + +#: src/Admin/Promos/About.php:411 +msgid "Connect with over 6,000+ apps and services" +msgstr "" + +#: src/Admin/Promos/About.php:415 +msgid "Support selling in multiple currencies" +msgstr "" + +#: src/Admin/Promos/About.php:419 +msgid "Collect product reviews from your customers" +msgstr "" + +#: src/Admin/Promos/About.php:423 +msgid "Add product recommendations, cross-sells, and upsells" +msgstr "" + +#: src/Admin/Promos/About.php:427 +msgid "Create advanced purchase receipt conditions" +msgstr "" + +#. translators: 1: opening link tag, 2. closing link tag. +#: src/Admin/Promos/About.php:446 +msgid "%1$sUpgrade to Pro Today%2$s" +msgstr "" + +#: src/Admin/Promos/About.php:456 +msgid "Bonus: Free Easy Digital Downloads users get 50% off regular price, automatically applied at checkout." +msgstr "" + +#: src/Admin/Promos/About.php:473 +msgid "An Introduction to Easy Digital Downloads" +msgstr "" + +#: src/Admin/Promos/About.php:475 +msgid "If you're already generally familiar with eCommerce in WordPress then this document should help you get up to speed with Easy Digital Downloads quite quickly. Unless otherwise noted, everything in this document deals with core features." +msgstr "" + +#: src/Admin/Promos/About.php:479 +msgid "How to Install and Activate EDD Extensions" +msgstr "" + +#: src/Admin/Promos/About.php:481 +msgid "Would you like to access Easy Digital Downloads extensions to extend the functionality of your store? Each EDD pass level comes with its own set of extensions to help you get the most out of your store." +msgstr "" + +#: src/Admin/Promos/About.php:485 +msgid "Using the Included Easy Digital Downloads Blocks" +msgstr "" + +#: src/Admin/Promos/About.php:487 +msgid "Creating your store has never been easier than when using the included Easy Digital Downloads Blocks, fully integrated with the WordPress Block Editor. Learn about what blocks come with Easy Digital Downloads and how you can use them on your store." +msgstr "" + +#: src/Admin/Promos/About.php:491 +msgid "Configuring Cache for Easy Digital Downloads" +msgstr "" + +#: src/Admin/Promos/About.php:493 +msgid "Caching plugins and services are designed to help ensure your site responds as quickly as possible. We understand that a fast store converts better than a slow store. We've worked with multiple caching solutions to write up guides on how to configure their plugin or services to work best with Easy Digital Downloads." +msgstr "" + +#: src/Admin/Promos/About.php:514 +msgid "Read Documentation" +msgstr "" + +#: src/Admin/Promos/About.php:537 +msgid "OptinMonster" +msgstr "" + +#: src/Admin/Promos/About.php:538 +msgid "Instantly get more subscribers, leads, and sales with the #1 conversion optimization toolkit. Create high converting popups, announcement bars, spin a wheel, and more with smart targeting and personalization." +msgstr "" + +#: src/Admin/Promos/About.php:545 +msgid "MonsterInsights" +msgstr "" + +#: src/Admin/Promos/About.php:546 +msgid "The leading WordPress analytics plugin that shows you how people find and use your website, so you can make data driven decisions to grow your business. Properly set up Google Analytics without writing code." +msgstr "" + +#: src/Admin/Promos/About.php:551 +msgid "MonsterInsights Pro" +msgstr "" + +#: src/Admin/Promos/About.php:559 +msgid "WP Mail SMTP" +msgstr "" + +#: src/Admin/Promos/About.php:560 +msgid "Improve your WordPress email deliverability and make sure that your website emails reach user's inbox with the #1 SMTP plugin for WordPress. Over 3 million websites use it to fix WordPress email issues." +msgstr "" + +#: src/Admin/Promos/About.php:565 +msgid "WP Mail SMTP Pro" +msgstr "" + +#: src/Admin/Promos/About.php:573 +msgid "AIOSEO" +msgstr "" + +#: src/Admin/Promos/About.php:574 +msgid "The original WordPress SEO plugin and toolkit that improves your website's search rankings. Comes with all the SEO features like Local SEO, WooCommerce SEO, sitemaps, SEO optimizer, schema, and more." +msgstr "" + +#: src/Admin/Promos/About.php:579 +msgid "AIOSEO Pro" +msgstr "" + +#: src/Admin/Promos/About.php:587 +msgid "SeedProd" +msgstr "" + +#: src/Admin/Promos/About.php:588 +msgid "The fastest drag & drop landing page builder for WordPress. Create custom landing pages without writing code, connect them with your CRM, collect subscribers, and grow your audience. Trusted by 1 million sites." +msgstr "" + +#: src/Admin/Promos/About.php:593 +msgid "SeedProd Pro" +msgstr "" + +#: src/Admin/Promos/About.php:601 +msgid "RafflePress" +msgstr "" + +#: src/Admin/Promos/About.php:602 +#: src/Admin/Promos/About.php:609 +msgid "Turn your website visitors into brand ambassadors! Easily grow your email list, website traffic, and social media followers with the most powerful giveaways & contests plugin for WordPress." +msgstr "" + +#: src/Admin/Promos/About.php:608 +msgid "RafflePress Pro" +msgstr "" + +#: src/Admin/Promos/About.php:617 +msgid "PushEngage" +msgstr "" + +#: src/Admin/Promos/About.php:618 +msgid "Connect with your visitors after they leave your website with the leading web push notification software. Over 10,000+ businesses worldwide use PushEngage to send 15 billion notifications each month." +msgstr "" + +#: src/Admin/Promos/About.php:625 +msgid "Smash Balloon Instagram Feeds" +msgstr "" + +#: src/Admin/Promos/About.php:626 +msgid "Easily display Instagram content on your WordPress site without writing any code. Comes with multiple templates, ability to show content from multiple accounts, hashtags, and more. Trusted by 1 million websites." +msgstr "" + +#: src/Admin/Promos/About.php:631 +msgid "Smash Balloon Instagram Feeds Pro" +msgstr "" + +#: src/Admin/Promos/About.php:639 +msgid "Smash Balloon Facebook Feeds" +msgstr "" + +#: src/Admin/Promos/About.php:640 +#: src/Admin/Promos/About.php:647 +msgid "Easily display Facebook content on your WordPress site without writing any code. Comes with multiple templates, ability to embed albums, group content, reviews, live videos, comments, and reactions." +msgstr "" + +#: src/Admin/Promos/About.php:646 +msgid "Smash Balloon Facebook Feeds Pro" +msgstr "" + +#: src/Admin/Promos/About.php:655 +msgid "Smash Balloon YouTube Feeds" +msgstr "" + +#: src/Admin/Promos/About.php:656 +msgid "Easily display YouTube videos on your WordPress site without writing any code. Comes with multiple layouts, ability to embed live streams, video filtering, ability to combine multiple channel videos, and more." +msgstr "" + +#: src/Admin/Promos/About.php:661 +msgid "Smash Balloon YouTube Feeds Pro" +msgstr "" + +#: src/Admin/Promos/About.php:669 +msgid "Smash Balloon Twitter Feeds" +msgstr "" + +#: src/Admin/Promos/About.php:670 +msgid "Easily display Twitter content in WordPress without writing any code. Comes with multiple layouts, ability to combine multiple Twitter feeds, Twitter card support, tweet moderation, and more." +msgstr "" + +#: src/Admin/Promos/About.php:675 +msgid "Smash Balloon Twitter Feeds Pro" +msgstr "" + +#: src/Admin/Promos/About.php:683 +msgid "TrustPulse" +msgstr "" + +#: src/Admin/Promos/About.php:684 +msgid "Boost your sales and conversions by up to 15% with real-time social proof notifications. TrustPulse helps you show live user activity and purchases to help convince other users to purchase." +msgstr "" + +#: src/Admin/Promos/About.php:691 +msgid "SearchWP" +msgstr "" + +#: src/Admin/Promos/About.php:692 +msgid "The most advanced WordPress search plugin. Customize your WordPress search algorithm, reorder search results, track search metrics, and everything you need to leverage search to grow your business." +msgstr "" + +#: src/Admin/Promos/About.php:700 +msgid "AffiliateWP" +msgstr "" + +#: src/Admin/Promos/About.php:701 +msgid "The #1 affiliate management plugin for WordPress. Easily create an affiliate program for your eCommerce store or membership site within minutes and start growing your sales with the power of referral marketing." +msgstr "" + +#: src/Admin/Promos/About.php:709 +msgid "WP Simple Pay" +msgstr "" + +#: src/Admin/Promos/About.php:710 +msgid "The #1 Stripe payments plugin for WordPress. Start accepting one-time and recurring payments on your WordPress site without setting up a shopping cart. No code required." +msgstr "" + +#: src/Admin/Promos/About.php:715 +msgid "WP Simple Pay Pro" +msgstr "" + +#: src/Admin/Promos/About.php:723 +msgid "WPForms" +msgstr "" + +#: src/Admin/Promos/About.php:724 +msgid "The best drag & drop WordPress form builder. Easily create beautiful contact forms, surveys, payment forms, and more with our 100+ form templates. Trusted by over 4 million websites as the best forms plugin." +msgstr "" + +#: src/Admin/Promos/About.php:729 +msgid "WPForms Pro" +msgstr "" + +#: src/Admin/Promos/About.php:737 +msgid "Sugar Calendar" +msgstr "" + +#: src/Admin/Promos/About.php:738 +msgid "A simple & powerful event calendar plugin for WordPress that comes with all the event management features including payments, scheduling, timezones, ticketing, recurring events, and more." +msgstr "" + +#: src/Admin/Promos/About.php:743 +msgid "Sugar Calendar Pro" +msgstr "" + +#: src/Admin/Promos/About.php:751 +msgid "WP Charitable" +msgstr "" + +#: src/Admin/Promos/About.php:752 +msgid "Top-rated WordPress donation and fundraising plugin. Over 10,000+ non-profit organizations and website owners use Charitable to create fundraising campaigns and raise more money online." +msgstr "" + +#: src/Admin/Promos/About.php:759 +msgid "WPCode" +msgstr "" + +#: src/Admin/Promos/About.php:760 +msgid "Future proof your WordPress customizations with the most popular code snippet management plugin for WordPress. Trusted by over 1,500,000+ websites for easily adding code to WordPress right from the admin area." +msgstr "" + +#: src/Admin/Promos/About.php:765 +msgid "WPCode Pro" +msgstr "" + +#: src/Admin/Promos/About.php:773 +msgid "Duplicator" +msgstr "" + +#: src/Admin/Promos/About.php:774 +msgid "Leading WordPress backup & site migration plugin. Over 1,500,000+ smart website owners use Duplicator to make reliable and secure WordPress backups to protect their websites. It also makes website migration really easy." +msgstr "" + +#: src/Admin/Promos/About.php:779 +msgid "Duplicator Pro" +msgstr "" + +#: src/Admin/Promos/About.php:835 +msgid "Installed & Active" +msgstr "" + +#: src/Admin/Promos/About.php:847 +msgid "Not Installed" +msgstr "" + +#: src/Admin/Promos/About.php:848 +msgid "Install Plugin" +msgstr "" + +#: src/Admin/Promos/Footer/FlyoutMenu.php:43 +msgid "See Quick Links" +msgstr "" + +#: src/Admin/Promos/Footer/FlyoutMenu.php:96 +#: src/Admin/SiteHealth/Licenses.php:116 +msgid "Upgrade to EDD (Pro)" +msgstr "" + +#: src/Admin/Promos/Footer/FlyoutMenu.php:113 +msgid "Activate Your License" +msgstr "" + +#: src/Admin/Promos/Footer/FlyoutMenu.php:125 +msgid "Get a License" +msgstr "" + +#: src/Admin/Promos/Footer/FlyoutMenu.php:153 +msgid "Contact Support" +msgstr "" + +#: src/Admin/Promos/Footer/FlyoutMenu.php:160 +msgid "View Documentation" +msgstr "" + +#: src/Admin/Promos/Footer/Links.php:35 +msgid "Support" +msgstr "" + +#: src/Admin/Promos/Footer/Links.php:46 +msgid "Docs" +msgstr "" + +#: src/Admin/Promos/Footer/Links.php:55 +msgid "Free Plugins" +msgstr "" + +#: src/Admin/Promos/Footer/Links.php:64 +msgid "Made with ♥ by the Easy Digital Downloads Team" +msgstr "" + +#: src/Admin/Promos/Footer/Links.php:131 +msgid "X (Formerly Twitter)" +msgstr "" + +#. translators: $1$s - Easy Digital Downloads plugin name, $2$s - Full markup for WP.org review link, $3$s - Full markup for WP.org review link. +#: src/Admin/Promos/Footer/Review.php:30 +msgid "Please rate %1$s %2$s on %3$s to help us spread the word." +msgstr "" + +#: src/Admin/Promos/Notices/Emails.php:48 +#: src/Admin/Promos/Notices/Emails.php:88 +msgid "Need More Emails?" +msgstr "" + +#: src/Admin/Promos/Notices/Emails.php:50 +msgid "Super charge your eCommerce store with these extensions which support custom emails:" +msgstr "" + +#: src/Admin/Promos/Notices/Emails.php:90 +msgid "Super charge your ecommerce store with custom emails:" +msgstr "" + +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:87 +msgid "Hey, I noticed you've made quite a few sales with Easy Digital Downloads! Are you enjoying Easy Digital Downloads?" +msgstr "" + +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:90 +msgid "Not Really" +msgstr "" + +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:94 +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 "" + +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:96 +msgid "Give Feedback" +msgstr "" + +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:97 +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:105 +msgid "No thanks" +msgstr "" + +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:101 +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 "" + +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:102 +msgid "~ Chris Klosowski
    President of Easy Digital Downloads" +msgstr "" + +#: src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php:104 +msgid "Ok, you deserve it!" +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:150 +msgid "Dismiss notice" +msgstr "" + +#: src/Admin/Promos/Notices/PriceChanges.php:57 +msgid "Deleting a price option with existing sales can cause issues with your store reporting and customer experience. Are you sure you want to delete this price ID?" +msgstr "" + +#: src/Admin/Promos/Notices/PriceChanges.php:71 +msgid "Yes, delete it" +msgstr "" + +#: src/Admin/Promos/Notices/PriceChanges.php:74 +msgid "No, keep it" +msgstr "" + +#: src/Admin/Promos/PromoHandler.php:154 +#: src/Admin/Promos/PromoHandler.php:188 +msgid "Missing notice ID." +msgstr "" + +#: src/Admin/Sections.php:331 +msgid "Empty" +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/Invoices.php:72 +msgid "Attractive Invoices For Your Customers" +msgstr "" + +#: src/Admin/Settings/Invoices.php:75 +msgid "Generate Attractive Invoices" +msgstr "" + +#: src/Admin/Settings/Invoices.php:76 +msgid "Build Customer Confidence" +msgstr "" + +#: src/Admin/Settings/Invoices.php:77 +msgid "PDF Download Support" +msgstr "" + +#: src/Admin/Settings/Invoices.php:78 +msgid "Include in Purchase Emails" +msgstr "" + +#: src/Admin/Settings/Invoices.php:79 +msgid "Customizable Templates" +msgstr "" + +#: src/Admin/Settings/Invoices.php:92 +msgid "Impress customers and build customer loyalty with attractive invoices. Making it easy to locate, save, and print purchase history builds trust with customers." +msgstr "" + +#: src/Admin/Settings/Invoices.php:93 +msgid "Provide a professional experience with customizable templates and one-click PDF downloads. " +msgstr "" + +#: src/Admin/Settings/Invoices.php:110 +msgid "Invoices" +msgstr "" + +#: src/Admin/Settings/Pointers.php:51 +msgid "Update your Store's Country" +msgstr "" + +#: src/Admin/Settings/Pointers.php:52 +msgid "Your store does not have the Business Country set. Easy Digital Downloads uses this setting to properly configure settings for your store. Please update your country to get the best experience." +msgstr "" + +#: src/Admin/Settings/Recurring.php:82 +msgid "Increase Revenue By Selling Subscriptions!" +msgstr "" + +#: src/Admin/Settings/Recurring.php:85 +msgid "Flexible Recurring Payments" +msgstr "" + +#: src/Admin/Settings/Recurring.php:86 +msgid "Custom Reminder Emails" +msgstr "" + +#: src/Admin/Settings/Recurring.php:87 +msgid "Free Trial Support" +msgstr "" + +#: src/Admin/Settings/Recurring.php:88 +msgid "Signup Fees" +msgstr "" + +#: src/Admin/Settings/Recurring.php:89 +msgid "Recurring Revenue Reports" +msgstr "" + +#: src/Admin/Settings/Recurring.php:102 +msgid "Grow stable income by selling subscriptions and make renewals hassle free for your customers." +msgstr "" + +#: src/Admin/Settings/Recurring.php:103 +msgid "When your customers are automatically billed, you reduce the risk of missed payments and retain more customers." +msgstr "" + +#: src/Admin/Settings/Recurring.php:120 +#: src/Gateways/Stripe/Admin/Settings.php:299 +#: src/Gateways/Stripe/Admin/Settings.php:305 +msgid "Subscriptions" +msgstr "" + +#: src/Admin/Settings/Reviews.php:73 +msgid "Build Trust With Real Customer Reviews" +msgstr "" + +#: src/Admin/Settings/Reviews.php:79 +msgid "Request Reviews" +msgstr "" + +#: src/Admin/Settings/Reviews.php:80 +msgid "Incentivize Reviewers" +msgstr "" + +#: src/Admin/Settings/Reviews.php:81 +msgid "Full Schema.org Support" +msgstr "" + +#: src/Admin/Settings/Reviews.php:82 +msgid "Embed Reviews Via Blocks" +msgstr "" + +#: src/Admin/Settings/Reviews.php:83 +msgid "Limit Reviews to Customers" +msgstr "" + +#: src/Admin/Settings/Reviews.php:84 +msgid "Vendor Reviews (with Frontend Submissions)" +msgstr "" + +#: src/Admin/Settings/Reviews.php:98 +msgid "Increase sales on your site with social proof. 70% of online shoppers don't purchase before reading reviews." +msgstr "" + +#: src/Admin/Settings/Reviews.php:99 +msgid "Easily collect, manage, and beautifully display reviews all from your WordPress dashboard." +msgstr "" + +#: src/Admin/Settings/Reviews.php:116 +msgid "Reviews" +msgstr "" + +#: src/Admin/Settings/Reviews.php:136 +msgid "Product Reviews" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:47 +msgid "Template" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:48 +msgid "Choose a template. Once you've saved your changes, preview an email to see the new template." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:55 +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:61 +msgid "This should be your site or shop name. Defaults to Site Title if empty." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:69 +msgid "This will act as the \"from\" and \"reply-to\" addresses." +msgstr "" + +#. translators: %s: admin email +#: src/Admin/Settings/Tabs/Emails.php:79 +msgid "Enter the email address(es) that may receive admin notices. One per line. Leave blank to use %s." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:95 +msgid "Purchase Email Subject" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:96 +msgid "Enter the subject line for the purchase receipt email." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:98 +#: src/Admin/Settings/Tabs/Emails.php:105 +#: src/Admin/Settings/Tabs/Emails.php:109 +#: src/Emails/Templates/OrderReceipt.php:55 +#: src/Emails/Templates/OrderReceipt.php:76 +#: src/Emails/Templates/OrderReceipt.php:77 +msgid "Purchase Receipt" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:102 +msgid "Purchase Email Heading" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:103 +msgid "Enter the heading for the purchase receipt email." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:110 +#: src/Emails/Templates/OrderReceipt.php:65 +msgid "Text to email customers after completing a purchase. Personalize with HTML and {tag} markers." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:115 +msgid "Disable Order Receipt" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:116 +msgid "Do not send purchase receipt emails to customers through Easy Digital Downloads." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:123 +msgid "Sale Notification Subject" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:124 +msgid "Enter the subject line for the sale notification email." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:130 +msgid "Sale Notification Heading" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:131 +msgid "Enter the heading for the sale notification email." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:133 +#: src/Emails/Templates/AdminOrderNotice.php:91 +msgid "New Sale!" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:137 +msgid "Sale Notification" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:138 +#: src/Emails/Templates/AdminOrderNotice.php:75 +msgid "Text to email as a notification for every completed purchase. Personalize with HTML and {tag} markers." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:143 +msgid "Disable Admin Notifications" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:144 +msgid "Check this box if you do not want to receive sales notification emails." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:162 +msgid "Email Frequency" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:167 +msgid "Weekly" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:168 +msgid "Monthly" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:173 +msgid "Email Recipient" +msgstr "" + +#. translators: email +#: src/Admin/Settings/Tabs/Emails.php:178 +msgid "Administrator: %s" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:186 +msgid "Enter the email address(es) that should receive Email Summaries. One per line." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:201 +msgid "Send Report Summaries" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:203 +msgid "Enable the summary emails." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:232 +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:239 +msgid "The next summary email is scheduled to send on %s." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:46 +msgid "Active Gateways" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:47 +msgid "Choose the payment gateways you want to enable." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:53 +msgid "Default Gateway" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:54 +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:60 +msgid "Payment Method Icons" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:61 +msgid "Display icons for the selected payment methods." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:61 +msgid "You will also need to configure your gateway settings if you are accepting credit cards." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:78 +msgid "Enforce SSL on Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:79 +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:87 +#: src/Admin/Settings/Tabs/Gateways.php:90 +msgid "Redirect to Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:88 +msgid "Immediately redirect to checkout after adding an item to the cart?" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:91 +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:95 +msgid "Customer Registration" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:97 +msgid "You may allow customers to place orders without a user account." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:99 +msgid "Setting this to auto will create a user account if one does not exist for a customer." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:101 +msgid "Allow customers to place orders without an account" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:102 +msgid "Customers must log in or create an account to purchase" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:103 +msgid "Automatically register new user accounts" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:109 +msgid "Enable Cart Saving" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:110 +msgid "Allow users to temporarily save their cart at checkout." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:112 +msgid "Cart Saving" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:113 +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:117 +msgid "Geolocation Detection" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:127 +#: src/Admin/Settings/Tabs/Gateways.php:130 +msgid "Moderation" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:131 +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:136 +msgid "Emails placed in the box above will not be allowed to make purchases." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:136 +msgid "One per line, enter: email addresses, domains (@example.com), or TLDs (.gov)." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:138 +msgid "@example.com" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:148 +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:152 +msgid "Default Status" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:153 +msgid "This will be the store default. It can be changed at a per-product level." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:161 +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:184 +msgid "Enable Test Mode" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:185 +msgid "What is Test Mode?" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:186 +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 your checkout process." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:200 +msgid "Forced Test Mode" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:201 +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:217 +msgid "Enable SKU Entry" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:218 +msgid "SKUs will be shown on purchase receipt and exported purchase histories." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:223 +msgid "Enable Sequential Numbering" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:224 +msgid "Sequential Order Numbers" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:225 +msgid "This setting will not impact previous orders. Future orders will be assigned a sequential number." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:231 +msgid "Sequential Number Prefix" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:232 +msgid "A prefix to prepend to all sequential order numbers." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:237 +msgid "Sequential Number Postfix" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:238 +msgid "A postfix to append to all sequential order numbers." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:246 +msgid "Advanced Order Numbers" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:265 +msgid "Sequential Starting Number" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:266 +msgid "The number at which the sequence should begin." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:274 +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:278 +msgid "The next order number will be %s." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:293 +msgid "Gain access to even more control over your order numbering!" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:295 +msgid "Track free orders in a separate sequential series" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:296 +msgid "Assign temporary numbers to incomplete orders" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:297 +msgid "Abandoned orders do not interrupt the complete order series" +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: src/Admin/Settings/Tabs/Gateways.php:319 +msgid "Access %1$sAdvanced Sequential Order Numbers%2$s today." +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: src/Admin/Settings/Tabs/Gateways.php:352 +msgid "GeoLocation Detection is only available in Easy Digital Downloads Pro. %1$sVerify your pass to get access to pro features.%2$s" +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: src/Admin/Settings/Tabs/Gateways.php:368 +msgid "Increase conversions by auto-filling address information for customers during checkout. To enable GeoLocation Detection, %1$sUpgrade to Pro%2$s." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:383 +msgid "Show Register / Login Form" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:384 +msgid "Display the registration and login forms on the checkout page for non-logged-in users." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:388 +msgid "Registration and Login Forms" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:389 +msgid "Registration Form Only" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:390 +msgid "Login Form Only" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:45 +msgid "Business Info" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:48 +msgid "Business Information" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:49 +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:53 +msgid "Business Name" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:54 +msgid "The official (legal) name of your store. Defaults to Site Title if empty." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:61 +msgid "Business Type" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:62 +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:65 +msgid "Individual" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:66 +msgid "Company" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:71 +msgid "Business Address" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:77 +msgid "Business Address (Extra)" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:83 +msgid "Business City" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:89 +msgid "Business Postal Code" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:96 +msgid "Business Country" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:108 +msgid "Business Region" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:121 +msgid "Page Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:122 +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:126 +msgid "Primary Checkout Page" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:127 +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:136 +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:145 +msgid "This is the page buyers are sent to if their transaction is cancelled or fails." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:154 +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:165 +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:180 +msgid "Currency Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:181 +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:186 +msgid "Choose your currency. Note that some payment gateways have currency restrictions." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:193 +msgid "Currency Position" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:194 +msgid "Choose the location of the currency sign." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:197 +msgid "Before ($10)" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:198 +msgid "After (10$)" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:203 +msgid "Thousands Separator" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:204 +msgid "The symbol to separate thousands. Usually , or .." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:213 +msgid "Decimal Separator" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:214 +msgid "The symbol to separate decimal points. Usually , or .." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:228 +msgid "API Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:229 +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:233 +msgid "Allow User Keys" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:234 +msgid "Allow all users to generate API keys." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:235 +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:243 +msgid "Visit the REST API documentation for further information." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:275 +msgid "Request Logs" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:276 +msgid "Log public API requests." +msgstr "" + +#. translators: 1: opening anchor tag, 2: closing anchor tag +#: src/Admin/Settings/Tabs/General.php:279 +msgid "Authenticated requests to the EDD API are always logged. %1$sView the API request logs.%2$s" +msgstr "" + +#: src/Admin/Settings/Tabs/Marketing.php:43 +msgid "Abandoned Cart Recovery" +msgstr "" + +#: src/Admin/Settings/Tabs/Marketing.php:49 +msgid "Multiple Discounts" +msgstr "" + +#: src/Admin/Settings/Tabs/Marketing.php:50 +msgid "Allow customers to use multiple discounts on the same purchase?" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:70 +msgid "Debug Mode" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:71 +msgid "Record important information to the debug log while troubleshooting." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:76 +msgid "Session Handling" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:80 +msgid "PHP Sessions" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:81 +msgid "Database Sessions" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:83 +msgid "Choose how you want to handle sessions. PHP based sessions are generally faster, but if you are experiencing issues with empty carts, database sessions may be more reliable." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:87 +msgid "Disable Styles" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:88 +msgid "Disable general EDD core styles for buttons, checkout fields, product pages, and other elements. EDD blocks will still load minimal styles." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:90 +msgid "Disabling Styles" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:91 +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:95 +msgid "Cart Item Quantities" +msgstr "" + +#. translators: %s: Downloads plural label +#: src/Admin/Settings/Tabs/Misc.php:97 +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:103 +msgid "Remove Data on Uninstall" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:104 +msgid "Completely remove all EDD core data when the plugin is deleted." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:111 +msgid "Default Button Style" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:112 +msgid "Choose the style you want to use for the buttons." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:118 +msgid "Default Button Color" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:119 +msgid "Choose the color you want to use for the buttons." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:126 +msgid "Complete Purchase Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:127 +msgid "The button label for completing a purchase." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:133 +msgid "Complete Free Purchase Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:134 +msgid "The button label for completing a free purchase." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:140 +msgid "Add to Cart Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:141 +msgid "Text shown on the Add to Cart Buttons." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:147 +msgid "Checkout Button Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:148 +msgid "Text shown on the Add to Cart Button when the product is already in the cart." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:157 +#: src/Admin/Settings/Tabs/Misc.php:159 +msgid "Require Login" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:158 +msgid "Require a user to login before file download links deliver the file." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:160 +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:165 +#: src/Admin/Settings/Tabs/Misc.php:168 +msgid "Download Method" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:166 +msgid "Select the file download method. Note, not all methods work on all servers." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:169 +msgctxt "Tooltip Display: Quotations must use escaped single quotes, for example \\'forced\\'" +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:175 +msgid "Forced" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:176 +msgid "Redirect" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:181 +msgid "Symbolically Link Files" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:182 +msgid "Check this if you are delivering really large files or having problems with file downloads completing." +msgstr "" + +#. translators: %s: Download singular label +#: src/Admin/Settings/Tabs/Misc.php:189 +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:192 +msgid "File Download Limits" +msgstr "" + +#. translators: %s: Download singular label +#: src/Admin/Settings/Tabs/Misc.php:194 +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:198 +#: src/Admin/Settings/Tabs/Misc.php:200 +msgid "Download Link Expiration" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:199 +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:201 +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:209 +msgid "Limit File Access" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:210 +msgid "Only give customers access to download links right after they make a purchase." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:212 +msgid "Limiting Access" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:213 +msgctxt "It is important to escape any quotations within this string, specifically \\'File Download Limit\\'" +msgid "This will prevent customers from viewing download links on your site after their initial purchase session has expired. This does not restrict the number of times a file can be downloaded; you can set a limit on the number of times a user can download a file with the 'File Download Limit' setting." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:250 +msgid "Buy Now Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:251 +msgid "Text shown on the Buy Now Buttons." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:261 +msgid "Buy Now Disabled" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:262 +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:281 +msgid "View the Log" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:46 +msgid "Privacy Policy Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:47 +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:51 +#: src/Admin/Settings/Tabs/Privacy.php:81 +msgid "Agreement" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:52 +msgid "Customers must agree to your privacy policy before purchasing." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:57 +#: src/Admin/Settings/Tabs/Privacy.php:87 +msgid "Agreement Label" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:58 +msgid "Label for the \"Agree to Privacy Policy\" checkbox." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:60 +msgid "I agree to the privacy policy" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:65 +msgid "Privacy Policy on Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:66 +msgid "Display your Privacy Policy on checkout." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:66 +msgid "Set your Privacy Policy here" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:76 +msgid "Terms & Agreements Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:77 +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:82 +msgid "Customers must agree to your terms before purchasing." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:88 +msgid "Label for the \"Agree to Terms\" checkbox." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:89 +msgid "I agree to the terms" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:95 +msgid "Agreement Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:115 +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:116 +msgid "What settings should I use?" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:117 +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:121 +msgid "Rules" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:123 +msgid "When a user wants their order history anonymized or removed, the following rules will be used:" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:128 +msgid "Do Nothing" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:129 +msgid "Anonymize" +msgstr "" + +#. translators: %s: Tab class name. +#: src/Admin/Settings/Tabs/Tab.php:81 +msgid "The %s settings class is missing the required ID property." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:43 +msgid "Enable Taxes" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:45 +msgid "Enabling Taxes" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:46 +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:52 +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:57 +msgid "Prices Include Tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:58 +msgid "This option affects how you enter prices." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:62 +msgid "Yes, I will enter prices inclusive of tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:63 +msgid "No, I will enter prices exclusive of tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:65 +msgid "Prices Inclusive of Tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:66 +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:70 +msgid "Show Tax Rate on Prices" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:71 +msgid "Some countries require a notice that product prices include tax." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:76 +msgid "Show in Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:77 +msgid "Should prices on the checkout page be shown with or without tax?" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:81 +msgid "Including tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:82 +msgid "Excluding tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:84 +msgid "Taxes Displayed for Products on Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:85 +msgid "This option will determine whether the product price displays with or without tax on checkout." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:103 +msgid "Regional Rates" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:104 +msgid "Configure rates for each region you wish to collect sales tax in." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:119 +msgid "Default Rate" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:121 +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:87 +msgid "Improve Email Deliverability" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:106 +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:148 +msgid "Ensure your emails are always delivered with WP Mail SMTP" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:164 +msgid "Install & Activate WP Mail SMTP" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:169 +msgid "Activate WP Mail SMTP" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:187 +msgid "Configure WP Mail SMTP" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:191 +msgid "Run the WP Mail SMTP Setup Wizard" +msgstr "" + +#: src/Admin/SiteHealth/Cron.php:26 +msgid "Easy Digital Downloads — Cron Events" +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:86 +msgid "Fix the Checkout Page" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:90 +msgid "Your checkout page is using the legacy shortcode" +msgstr "" + +#. translators: 1: opening tag, 2. closing tag +#: src/Admin/SiteHealth/Direct.php:97 +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:106 +msgid "Edit Checkout Page" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:121 +msgid "Your download files are protected" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:126 +msgid "Your checkout page is a critical part of your store." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:149 +msgid "Your download files may not be protected" +msgstr "" + +#. translators: 1: opening link tag, 2: closing link tag +#: src/Admin/SiteHealth/Direct.php:156 +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:160 +msgid "If you have already done this, you can disregard this notice." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:172 +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:180 +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:191 +msgid "Protect your files" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:197 +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:210 +msgid "Your download files are currently not protected" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:233 +msgid "Miscellaneous Settings" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:250 +msgid "You have at least one gateway enabled" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:255 +msgid "Fantastic! You have enabled a gateway and can accept orders." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:267 +msgid "Your store is not accepting payments" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:270 +msgid "To process orders that require payment, you must have a gateway enabled." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:274 +msgid "A gateway is a service, such as PayPal or Stripe, that allows your store to accept payments." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:278 +msgid "Stores that offer multiple ways for their customers to pay see higher conversion rates." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:291 +msgid "Configure a Gateway" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:310 +msgid "Scheduled events are running" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:315 +msgid "Easy Digital Downloads uses scheduled events in a number of ways to help maintain performance and stability." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:325 +msgid "Scheduled events are not running" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:330 +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:331 +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/General.php:214 +msgid "Unknown" +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: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/Admin/Tools/Screen.php:70 +msgid "API Keys" +msgstr "" + +#: src/Admin/Tools/Screen.php:71 +msgid "Beta Versions" +msgstr "" + +#: src/Admin/Tools/Screen.php:73 +msgid "System Info" +msgstr "" + +#: src/Admin/Tools/Screen.php:75 +msgid "Import/Export" +msgstr "" + +#: src/Admin/Upgrades/v3/Customer_Addresses.php:31 +msgid "Customer addresses migration completed successfully." +msgstr "" + +#: src/Admin/Upgrades/v3/Customer_Email_Addresses.php:31 +msgid "Customer email addresses migration completed successfully." +msgstr "" + +#: src/Admin/Upgrades/v3/Customer_Notes.php:33 +msgid "Customer notes migration completed successfully." +msgstr "" + +#: src/Admin/Upgrades/v3/Data_Migrator.php:408 +#: src/Upgrades/Orders/MigrateAfterActionsDate.php:199 +msgid "After payment actions processed." +msgstr "" + +#: src/Admin/Upgrades/v3/Data_Migrator.php:1362 +#: src/CLI/Migration/Discounts.php:60 +msgid "Legacy Discount" +msgstr "" + +#: src/Admin/Upgrades/v3/Discounts.php:31 +msgid "Discounts migration completed successfully." +msgstr "" + +#: src/Admin/Upgrades/v3/Logs.php:31 +msgid "Logs migration completed successfully." +msgstr "" + +#: src/Admin/Upgrades/v3/Orders.php:31 +msgid "Orders migration completed successfully." +msgstr "" + +#: src/Admin/Upgrades/v3/Order_Notes.php:31 +msgid "Order notes migration completed successfully." +msgstr "" + +#: src/Admin/Upgrades/v3/Remove_Legacy_Data.php:42 +msgid "Legacy data removed successfully." +msgstr "" + +#: src/Admin/Upgrades/v3/Tax_Rates.php:31 +msgid "Tax rates migration completed successfully." +msgstr "" + +#: src/Assets/Localization.php:60 +msgid "You have already added this item to your cart" +msgstr "" + +#: src/Assets/Localization.php:61 +msgid "Your cart is empty" +msgstr "" + +#: src/Assets/Localization.php:96 +msgid "Please select a payment method" +msgstr "" + +#: src/Assets/Localization.php:97 +msgid "Please enter a discount code" +msgstr "" + +#: src/Assets/Localization.php:99 +msgid "Discount Applied" +msgstr "" + +#: src/Assets/Localization.php:100 +msgid "Please enter an email address before applying a discount code" +msgstr "" + +#: src/Assets/Localization.php:101 +msgid "Please enter a username before applying a discount code" +msgstr "" + +#: src/Assets/Localization.php:102 +msgid "Please Wait..." +msgstr "" + +#: src/Checkout/AutoRegister.php:126 +msgid "Your site registers a new user account when an order is placed. You may allow free downloads without account creation." +msgstr "" + +#: src/Checkout/Errors.php:134 +msgid "Email already used. Log in or use a different email to complete your purchase." +msgstr "" + +#: src/CLI/Migration/CustomerEmails.php:35 +msgid "No customers with missing emails were found." +msgstr "" + +#: src/CLI/Migration/CustomerEmails.php:78 +msgid "Missing Customer Emails Added:" +msgstr "" + +#: src/CLI/Migration/Discounts.php:42 +msgid "No orders with missing discounts were found." +msgstr "" + +#: src/CLI/Migration/Discounts.php:76 +msgid "Missing Discounts Added:" +msgstr "" + +#: src/Compat/Customer.php:194 +#: src/Compat/Discount.php:122 +#: src/Compat/Discount.php:172 +#: src/Compat/Log.php:67 +#: src/Compat/Log.php:215 +#: src/Compat/Payment.php:91 +#: src/Compat/Payment.php:133 +msgid "This function is not meant to be called directly. It is only here for backwards compatibility purposes." +msgstr "" + +#. translators: 1: posts table, 2: adjustments table, 3: edd_get_discounts(), 4: edd_get_discount(), 5: EDD_Discount, 6: development URL +#: src/Compat/Discount.php:78 +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 "" + +#: src/Compat/Log.php:275 +msgid "All log postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_get_api_request_log() instead." +msgstr "" + +#. translators: 1: wp_posts table, 2: edd_orders table, 3: edd_get_orders(), 4: edd_get_order(), 5: EDD development blog +#: src/Compat/Payment.php:107 +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 "" + +#: src/Compat/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 "" + +#. translators: 1: opening anchor tag (do not translate) 2. closing anchor tag (do not translate) +#: src/Compat/Template.php:111 +msgid "This update must be completed manually. Please click %1$shere%2$s for more information." +msgstr "" + +#: src/Compat/Template.php:112 +msgid "The file that needs to be updated is located at:" +msgstr "" + +#: src/Cron/Events/Event.php:96 +msgid "A hook and schedule are required to schedule an event." +msgstr "" + +#. translators: %s: hook name that would be run for this WP Cron event. +#: src/Cron/Events/Event.php:109 +msgid "The event %s is already scheduled." +msgstr "" + +#: src/Cron/Events/SingleEvent.php:134 +msgid "The hook name must be a string." +msgstr "" + +#: src/Cron/Events/SingleEvent.php:142 +msgid "The run time must be an integer." +msgstr "" + +#: src/Cron/Events/SingleEvent.php:146 +msgid "The run time must be set." +msgstr "" + +#: src/Cron/Events/SingleEvent.php:150 +msgid "This event is already scheduled." +msgstr "" + +#: src/Cron/Schedules/Schedule.php:75 +msgid "An ID, interval, and display name must be provided." +msgstr "" + +#: src/Cron/Schedules/Schedule.php:80 +msgid "The interval must be at least 5 minutes." +msgstr "" + +#: src/Cron/Schedules/SessionCleanup.php:40 +msgid "Easy Digital Downloads Session Cleanup" +msgstr "" + +#: src/Downloads/Search.php:133 +#: src/HTML/ProductSelect.php:270 +msgid "All Price Options" +msgstr "" + +#: src/Downloads/Services.php:101 +msgid "Downloads as Services" +msgstr "" + +#: src/Downloads/Services.php:102 +msgid "Select the categories that contain services, or products with no downloadable files." +msgstr "" + +#: src/Downloads/Services.php:107 +msgid "Select categories" +msgstr "" + +#: src/Emails/Base.php:183 +msgid "Default Template" +msgstr "" + +#: src/Emails/Base.php:184 +msgid "No template, plain text only" +msgstr "" + +#: src/Emails/Base.php:319 +msgid "You cannot send email with EDD_Emails until init/admin_init has been reached" +msgstr "" + +#. translators: 1: To address, 2: Subject +#: src/Emails/Base.php:351 +msgid "" +"Email from Easy Digital Downloads failed to send. \n" +"To: %1$s\n" +"Subject: %2$s\n" +"\n" +"" +msgstr "" + +#: src/Emails/Registry.php:61 +msgid "An email ID and class must be provided." +msgstr "" + +#: src/Emails/Registry.php:66 +msgid "The email ID provided is already registered." +msgstr "" + +#: src/Emails/Registry.php:71 +msgid "The email class must exist and extend the EDD\\Emails\\Types\\Email class." +msgstr "" + +#: src/Emails/Registry.php:101 +msgid "The email ID provided is not registered." +msgstr "" + +#. translators: 1: The number of arguments provided. 2: The number of arguments required. 3: The class name. +#: src/Emails/Registry.php:113 +msgid "The number of arguments provided (%1$d) does not match the number of arguments required (%2$d) for %3$s." +msgstr "" + +#: src/Emails/Tags/Registry.php:61 +msgid "Download List" +msgstr "" + +#: src/Emails/Tags/Registry.php:62 +msgid "A list of download links for each download purchased." +msgstr "" + +#: src/Emails/Tags/Registry.php:70 +msgid "File URLs" +msgstr "" + +#: src/Emails/Tags/Registry.php:71 +msgid "A plain-text list of download URLs for each download purchased." +msgstr "" + +#: src/Emails/Tags/Registry.php:78 +msgid "The buyer's (or user's) first name." +msgstr "" + +#: src/Emails/Tags/Registry.php:84 +msgid "Full Name" +msgstr "" + +#: src/Emails/Tags/Registry.php:85 +msgid "The buyer's (or user's) full name: first and last." +msgstr "" + +#: src/Emails/Tags/Registry.php:92 +msgid "The buyer's (or user's) user name on the site, if they registered an account." +msgstr "" + +#: src/Emails/Tags/Registry.php:99 +msgid "The buyer's (or user's) email address." +msgstr "" + +#: src/Emails/Tags/Registry.php:106 +msgid "The buyer's billing address." +msgstr "" + +#: src/Emails/Tags/Registry.php:112 +msgid "Purchase Date" +msgstr "" + +#: src/Emails/Tags/Registry.php:113 +msgid "The date of the purchase." +msgstr "" + +#: src/Emails/Tags/Registry.php:120 +msgid "The price of the purchase before taxes." +msgstr "" + +#: src/Emails/Tags/Registry.php:133 +msgid "Fees Total" +msgstr "" + +#: src/Emails/Tags/Registry.php:134 +msgid "The total fees on the order, formatted with currency." +msgstr "" + +#: src/Emails/Tags/Registry.php:140 +msgid "Fees List" +msgstr "" + +#: src/Emails/Tags/Registry.php:141 +msgid "A list of all fees on the order, with amounts." +msgstr "" + +#: src/Emails/Tags/Registry.php:154 +msgid "Payment ID" +msgstr "" + +#: src/Emails/Tags/Registry.php:155 +msgid "The unique identifier for this purchase." +msgstr "" + +#: src/Emails/Tags/Registry.php:161 +msgid "Receipt ID" +msgstr "" + +#: src/Emails/Tags/Registry.php:162 +msgid "The unique identifier for the receipt of this purchase." +msgstr "" + +#: src/Emails/Tags/Registry.php:169 +msgid "The method of payment used for this purchase." +msgstr "" + +#: src/Emails/Tags/Registry.php:175 +msgid "Site Name" +msgstr "" + +#: src/Emails/Tags/Registry.php:176 +msgid "Your site name." +msgstr "" + +#: src/Emails/Tags/Registry.php:183 +msgid "Links to the EDD success page with the text \"View Receipt\"." +msgstr "" + +#: src/Emails/Tags/Registry.php:189 +msgid "Receipt Link" +msgstr "" + +#: src/Emails/Tags/Registry.php:190 +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 "" + +#: src/Emails/Tags/Registry.php:196 +msgid "Discount Codes" +msgstr "" + +#: src/Emails/Tags/Registry.php:197 +msgid "Adds a list of any discount codes applied to this purchase." +msgstr "" + +#: src/Emails/Tags/Registry.php:204 +msgid "The buyer's IP Address." +msgstr "" + +#: src/Emails/Tags/Registry.php:210 +msgid "Login Link" +msgstr "" + +#: src/Emails/Tags/Registry.php:211 +msgid "The link to log into the site." +msgstr "" + +#: src/Emails/Tags/Registry.php:217 +msgid "Refund Link" +msgstr "" + +#: src/Emails/Tags/Registry.php:218 +msgid "The link to refund record in the EDD admin." +msgstr "" + +#: src/Emails/Tags/Registry.php:225 +msgid "Order Details Link" +msgstr "" + +#: src/Emails/Tags/Registry.php:226 +msgid "The link to the order details page in the EDD admin." +msgstr "" + +#: src/Emails/Tags/Registry.php:234 +msgid "The merchant transaction ID for this order. This is for admin emails only." +msgstr "" + +#: src/Emails/Tags/Registry.php:241 +msgid "Password Reset Link" +msgstr "" + +#: src/Emails/Tags/Registry.php:242 +msgid "The link to set the user's password. In an order receipt, this will only be included for the user's first purchase." +msgstr "" + +#: src/Emails/Tags/Registry.php:248 +msgid "Refund Amount" +msgstr "" + +#: src/Emails/Tags/Registry.php:249 +msgid "The amount that was refunded to the customer." +msgstr "" + +#: src/Emails/Tags/Registry.php:255 +msgid "Refund ID" +msgstr "" + +#: src/Emails/Tags/Registry.php:256 +msgid "The unique identifier for this refund." +msgstr "" + +#: src/Emails/Tags/Render.php:38 +msgid "Login" +msgstr "" + +#: src/Emails/Tags/Render.php:87 +msgid "Set your password" +msgstr "" + +#: src/Emails/Templates/AdminOrderNotice.php:65 +msgid "Admin Sale Notification" +msgstr "" + +#. translators: %s: The email tag that will be replaced with the payment ID. +#: src/Emails/Templates/AdminOrderNotice.php:90 +msgid "New download purchase - Order #%s" +msgstr "" + +#. translators: %s: The plural label for the Download post type. +#: src/Emails/Templates/AdminOrderNotice.php:120 +#: src/Emails/Templates/StripeEarlyFraudWarning.php:162 +msgid "Hello" +msgstr "" + +#. translators: %s: The plural label for the Download post type. +#: src/Emails/Templates/AdminOrderNotice.php:124 +msgid "A %s purchase has been made" +msgstr "" + +#. translators: %s: The plural label for the Download post type. +#: src/Emails/Templates/AdminOrderNotice.php:128 +#: src/Emails/Templates/StripeEarlyFraudWarning.php:167 +msgid "%s sold:" +msgstr "" + +#. translators: %s: The email tag that will be replaced by the customer's full name +#: src/Emails/Templates/AdminOrderNotice.php:133 +#: src/Emails/Templates/StripeEarlyFraudWarning.php:170 +msgid "Purchased by: %s" +msgstr "" + +#. translators: %s: The email tag that will be replaced by the order total. +#: src/Emails/Templates/AdminOrderNotice.php:136 +#: src/Emails/Templates/StripeEarlyFraudWarning.php:172 +msgctxt "Context: This is a tag (placholder) for email content that will be replaced when sending." +msgid "Amount: %s" +msgstr "" + +#. translators: %s: The email tag that will be replaced by the payment method. +#: src/Emails/Templates/AdminOrderNotice.php:139 +msgid "Payment Method: %s" +msgstr "" + +#: src/Emails/Templates/AdminOrderNotice.php:141 +msgid "Thank you" +msgstr "" + +#: src/Emails/Templates/AdminOrderRefund.php:73 +msgid "Admin Refund Notification" +msgstr "" + +#: src/Emails/Templates/AdminOrderRefund.php:83 +msgid "Text to email to admin(s) after issuing a refund." +msgstr "" + +#: src/Emails/Templates/AdminOrderRefund.php:94 +msgid "An order has been refunded" +msgstr "" + +#. translators: %s: The email tag that will be replaced with the order ID. +#: src/Emails/Templates/AdminOrderRefund.php:140 +msgid "Order %s has been refunded." +msgstr "" + +#: src/Emails/Templates/EmailTemplate.php:218 +msgid "This email cannot be disabled." +msgstr "" + +#: src/Emails/Templates/EmailTemplate.php:220 +msgid "This email cannot be enabled." +msgstr "" + +#: src/Emails/Templates/EmailTemplate.php:247 +msgid "This tag is required for this email." +msgstr "" + +#: src/Emails/Templates/NewUser.php:63 +msgid "New User Registration" +msgstr "" + +#: src/Emails/Templates/NewUser.php:73 +msgid "This email is sent to a new user when their account is registered via EDD." +msgstr "" + +#. translators: %s: Email tag that will be replaced with the Site's Name +#: src/Emails/Templates/NewUser.php:85 +msgctxt "Context: This is an email tag (placeholder) that will be replaced at the time of sending the email" +msgid "[%s] Your username and password" +msgstr "" + +#. translators: %s: Email tag that will be replaced with the username +#: src/Emails/Templates/NewUser.php:103 +#: src/Emails/Templates/NewUserAdmin.php:101 +#: src/Emails/Templates/PasswordReset.php:181 +msgctxt "Context: This is an email tag (placeholder) that will be replaced at the time of sending the email" +msgid "Username: %s" +msgstr "" + +#: src/Emails/Templates/NewUser.php:111 +msgid "Password: [entered on site]" +msgstr "" + +#: src/Emails/Templates/NewUserAdmin.php:63 +msgid "Admin New User Notification" +msgstr "" + +#: src/Emails/Templates/NewUserAdmin.php:73 +msgid "This email is sent to the store admin when a new user is registered." +msgstr "" + +#. translators: %s: Email tag that will be replaced with the user email +#: src/Emails/Templates/NewUserAdmin.php:111 +msgctxt "Context: This is an email tag (placeholder) that will be replaced at the time of sending the email" +msgid "E-mail: %s" +msgstr "" + +#: src/Emails/Templates/OrderReceipt.php:106 +msgid "Thank you for your purchase. Please click on the link(s) below to download your files." +msgstr "" + +#: src/Emails/Templates/OrderRefund.php:63 +msgid "Refund Issued" +msgstr "" + +#: src/Emails/Templates/OrderRefund.php:73 +msgid "Text to email customers after issuing a refund." +msgstr "" + +#: src/Emails/Templates/OrderRefund.php:84 +msgid "Your order has been refunded" +msgstr "" + +#: src/Emails/Templates/OrderRefund.php:130 +msgid "Your order has been refunded." +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:60 +msgid "Password Reset" +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:69 +msgid "This email is sent by WordPress when a user requests a password reset from the EDD Login block." +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:143 +msgid "This email cannot be disabled because it is managed and sent by WordPress." +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:145 +msgid "This email cannot be enabled because the login page has not been set." +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:147 +msgid "This email cannot be enabled because it has been disabled by code." +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:164 +msgid "Password Reset URL" +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:165 +msgid "The link for the user to reset their password." +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:176 +msgid "Someone has requested a password reset for the following account:" +msgstr "" + +#. translators: %s: Email tag that will be replaced with the site name +#: src/Emails/Templates/PasswordReset.php:178 +msgid "Site Name: %s" +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:188 +msgid "If this was a mistake, ignore this email and nothing will happen." +msgstr "" + +#: src/Emails/Templates/PasswordReset.php:189 +msgid "To reset your password, visit the following address:" +msgstr "" + +#. translators: %s: IP address of password reset requester. +#: src/Emails/Templates/PasswordReset.php:195 +msgid "This password reset request originated from the IP address %s." +msgstr "" + +#. translators: %s: Site name. +#: src/Emails/Templates/PasswordReset.php:219 +msgid "[%s] Password Reset" +msgstr "" + +#: src/Emails/Templates/Registry.php:79 +msgid "Admin" +msgstr "" + +#: src/Emails/Templates/Registry.php:95 +msgid "EDD Core" +msgstr "" + +#: src/Emails/Templates/Registry.php:96 +msgid "WordPress" +msgstr "" + +#: src/Emails/Templates/Registry.php:115 +msgid "Account" +msgstr "" + +#: src/Emails/Templates/Registry.php:132 +msgid "Add License Renewal Notice" +msgstr "" + +#: src/Emails/Templates/Registry.php:136 +msgid "Add Subscription Reminder" +msgstr "" + +#: src/Emails/Templates/Registry.php:140 +msgid "Add Per Product Email" +msgstr "" + +#: src/Emails/Templates/Registry.php:214 +msgid "Invalid email template." +msgstr "" + +#. translators: %1$s is the class name, %2$s is the parent class name. +#: src/Emails/Templates/Registry.php:219 +msgid "The %1$s class must extend the %2$s class." +msgstr "" + +#: src/Emails/Templates/StripeEarlyFraudWarning.php:80 +msgid "Stripe Early Fraud Warning" +msgstr "" + +#: src/Emails/Templates/StripeEarlyFraudWarning.php:90 +msgid "Be alerted when an early fraud warning is detected by Stripe's machine learning. Avoid disputes before they even happen by reviewing flagged orders to verify them." +msgstr "" + +#. translators: %s: Email tag that will be replaced with the order ID. +#: src/Emails/Templates/StripeEarlyFraudWarning.php:105 +msgid "Stripe Early Fraud Warning - Order #%s" +msgstr "" + +#: src/Emails/Templates/StripeEarlyFraudWarning.php:106 +msgid "Possible Fraudulent Order" +msgstr "" + +#: src/Emails/Templates/StripeEarlyFraudWarning.php:124 +msgid "This email is only available if the Stripe gateway is enabled and using the Payment Elements mode." +msgstr "" + +#: src/Emails/Templates/StripeEarlyFraudWarning.php:164 +msgid "Stripe has detected a potential fraudulent order." +msgstr "" + +#. translators: 1: The opening anchor tag, 2: The closing anchor tag +#: src/Emails/Templates/StripeEarlyFraudWarning.php:174 +msgid "%1$sOrder Details%2$s" +msgstr "" + +#: src/Emails/Templates/StripeEarlyFraudWarning.php:175 +msgid "Note: Once you have reviewed the order, ensure you take the appropriate action within your Stripe dashboard to help improve future fraud detection." +msgstr "" + +#: src/Emails/Templates/Traits/Actions.php:57 +msgid "Preview" +msgstr "" + +#: src/Emails/Templates/Traits/Actions.php:72 +msgid "Send Test" +msgstr "" + +#: src/Emails/Templates/UserVerification.php:63 +msgid "User Verification" +msgstr "" + +#: src/Emails/Templates/UserVerification.php:73 +msgid "This email is sent to a user when they need to verify their account." +msgstr "" + +#: src/Emails/Templates/UserVerification.php:84 +#: src/Emails/Templates/UserVerification.php:85 +msgid "Verify your account" +msgstr "" + +#: src/Emails/Templates/UserVerification.php:99 +msgid "Verification URL" +msgstr "" + +#: src/Emails/Templates/UserVerification.php:100 +msgid "The link for the user to verify their account." +msgstr "" + +#. translators: %s: The email tag that will be replaced with the customer's full name. +#: src/Emails/Templates/UserVerification.php:136 +msgid "Hello %s," +msgstr "" + +#. translators: %s: The email tag that will be replaced with the Site Name. +#: src/Emails/Templates/UserVerification.php:141 +msgid "Your account with %s needs to be verified before you can access your order history." +msgstr "" + +#. translators: %s: The email tag that will be replaced with the verification URL. +#: src/Emails/Templates/UserVerification.php:146 +msgid "Visit this link to verify your account: %s" +msgstr "" + +#: src/Emails/Types/NewUser.php:168 +msgid "Please Check Your Custom Code" +msgstr "" + +#: src/Emails/Types/NewUser.php:169 +msgid "EDD has detected that you are filtering the user registration email. To improve security and performance, in an upcoming release of EDD, the password will no longer be included in the email filter. Please verify and update any customizations you may have in place." +msgstr "" + +#. translators: %s: IPN Verification response +#: src/Gateways/PayPal/IPN.php:201 +#: src/Gateways/PayPal/IPN.php:212 +msgid "Invalid PayPal Commerce/Express IPN verification response. IPN data: %s" +msgstr "" + +#. translators: 1: Dispute ID, 2: Dispute reason code. Example: The PayPal transaction has been disputed. Case ID: PP-R-NMW-10060094. Reason given: non_receipt. +#: src/Gateways/PayPal/IPN.php:324 +msgid "The PayPal transaction has been disputed (IPN). Case ID: %1$s. Reason given: %2$s." +msgstr "" + +#: src/Gateways/PayPal/IPN.php:326 +msgid "unknown" +msgstr "" + +#. translators: %s: The payment data sent via the IPN +#: src/Gateways/PayPal/IPN.php:434 +msgid "Invalid Currency Code" +msgstr "" + +#. translators: %s: The payment data sent via the IPN +#: src/Gateways/PayPal/IPN.php:434 +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 +#: src/Gateways/PayPal/IPN.php:449 +msgid "Transaction ID %s failed in PayPal" +msgstr "" + +#. translators: %s: The collected outstanding balance of the subscription +#: src/Gateways/PayPal/IPN.php:479 +msgid "Outstanding subscription balance of %s collected successfully." +msgstr "" + +#. translators: %s: Transaction ID +#: src/Gateways/PayPal/IPN.php:579 +msgid "Reversal processed in PayPal (IPN). Transaction ID: %s" +msgstr "" + +#. translators: 1: Amount refunded; %2$s - Original payment ID; %3$s - Refund transaction ID +#: src/Gateways/PayPal/IPN.php:597 +#: src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Refunded.php:53 +msgid "Amount: %1$s; Payment transaction ID: %2$s; Refund transaction ID: %3$s" +msgstr "" + +#: src/Gateways/PayPal/IPN.php:609 +msgid "Partial refund processed in PayPal (IPN)." +msgstr "" + +#: src/Gateways/PayPal/IPN.php:619 +msgid "Full refund processed in PayPal (IPN)." +msgstr "" + +#. translators: 1: Dispute ID, 2: Dispute reason code. Example: The PayPal transaction has been disputed. Case ID: PP-R-NMW-10060094. Reason given: non_receipt. +#: src/Gateways/PayPal/Webhooks/Events/Customer_Dispute_Created.php:58 +msgid "The PayPal transaction has been disputed. Case ID: %1$s. Reason given: %2$s." +msgstr "" + +#. translators: dispute message added by the customer +#: src/Gateways/PayPal/Webhooks/Events/Customer_Dispute_Created.php:72 +msgid "PayPal Dispute Message: %s" +msgstr "" + +#: src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Completed.php:55 +msgid "Webhook Error" +msgstr "" + +#. translators: %s: webhook data +#: src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Completed.php:58 +msgid "Invalid payment amount in webhook response. Webhook data: %s" +msgstr "" + +#. translators: %1$s is the order amount, %2$s is the PayPal amount +#: src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Completed.php:70 +msgid "Payment failed due to invalid amount in PayPal webhook. Expected amount: %1$s; PayPal amount: %2$s." +msgstr "" + +#: src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Denied.php:31 +msgid "PayPal transaction denied." +msgstr "" + +#: src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Refunded.php:65 +msgid "Partial refund processed in PayPal." +msgstr "" + +#: src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Refunded.php:75 +msgid "Full refund processed in PayPal." +msgstr "" + +#: src/Gateways/Stripe/Admin/Connect.php:39 +msgid "Webhooks are configured correctly, but are not typically functional in local/development environments." +msgstr "" + +#. translators: 1: Webhooks setup link, 2: Closing anchor tag +#: src/Gateways/Stripe/Admin/Connect.php:43 +msgid "Webhooks are not typically functional in local/development environments, but you can %1$smanually create the webhooks%2$s for testing. This will happen automatically on production sites." +msgstr "" + +#. translators: 1: Webhooks setup link, 2: Closing anchor tag +#: src/Gateways/Stripe/Admin/Connect.php:55 +msgid "Webhooks cannot be automatically set up because your site is not using HTTPS. %1$sManually add webhooks%2$s." +msgstr "" + +#. translators: 1: Webhooks setup link, 2: Closing anchor tag, 3: Manual setup link +#: src/Gateways/Stripe/Admin/Connect.php:63 +msgid "Webhooks not found. %1$sAutomatically set up webhooks%2$s or %3$sadd them to your account manually%2$s." +msgstr "" + +#: src/Gateways/Stripe/Admin/Connect.php:70 +msgid "Webhooks are configured correctly." +msgstr "" + +#. translators: %1$s is the opening link tag; %2$s is the closing link tag. +#: src/Gateways/Stripe/Admin/LicenseManager.php:147 +msgid "Your license is not active. Please %1$sactivate your license%2$s." +msgstr "" + +#. translators: %1$s is the opening link tag; %2$s is the closing link tag. +#: src/Gateways/Stripe/Admin/LicenseManager.php:154 +msgid "Your license has expired. Please %1$srenew your license%2$s." +msgstr "" + +#: src/Gateways/Stripe/Admin/LicenseManager.php:248 +msgid "Easy Digital Downloads (Pro)" +msgstr "" + +#: src/Gateways/Stripe/Admin/LicenseManager.php:248 +msgid "Easy Digital Downloads - Stripe Pro Payment Gateway" +msgstr "" + +#: src/Gateways/Stripe/Admin/LicenseManager.php:252 +msgid "Easy Digital Downloads - Stripe Pro Payment Gateway Is Not Fully Activated!" +msgstr "" + +#: src/Gateways/Stripe/Admin/LicenseManager.php:253 +msgid "Activate your license key to receive important security and feature updates and remove application fees." +msgstr "" + +#: src/Gateways/Stripe/Admin/LicenseManager.php:258 +msgid "Complete Activation" +msgstr "" + +#. translators: %s: name of the license. +#: src/Gateways/Stripe/Admin/LicenseManager.php:272 +#: src/Gateways/Stripe/Admin/LicenseManager.php:298 +msgid "Your %s license has expired!" +msgstr "" + +#. translators: %s: date the grace period ends. +#: src/Gateways/Stripe/Admin/LicenseManager.php:277 +msgid "Renew your license before %s to continue using Stripe without paying additional fees and to continue receiving important security and feature updates." +msgstr "" + +#: src/Gateways/Stripe/Admin/LicenseManager.php:284 +#: src/Gateways/Stripe/Admin/LicenseManager.php:306 +#: src/Gateways/Stripe/Admin/LicenseManager.php:333 +msgid "Renew License" +msgstr "" + +#: src/Gateways/Stripe/Admin/LicenseManager.php:301 +msgid "You are now paying additional fees with every Stripe transaction. You are no longer receiving important security and feature updates for Stripe Pro." +msgstr "" + +#. translators: %s: name of the license. +#: src/Gateways/Stripe/Admin/LicenseManager.php:320 +msgid "Your %s License Is Expiring Soon!" +msgstr "" + +#. translators: 1: the name of the license, 2: the date the license expires. +#: src/Gateways/Stripe/Admin/LicenseManager.php:325 +msgid "Your %1$s license is set to expire on %2$s. An active license key is required to create and edit payment forms, enable automatic updates, and to keep Easy Digital Downloads - Stripe Pro Payment Gateway fully activated." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:42 +msgid "Test Publishable Key" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:43 +msgid "Enter your test publishable key, found in your Stripe Account Settings" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:50 +msgid "Test Secret Key" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:51 +msgid "Enter your test secret key, found in your Stripe Account Settings" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:58 +msgid "Live Publishable Key" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:59 +msgid "Enter your live publishable key, found in your Stripe Account Settings" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:66 +msgid "Live Secret Key" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:67 +msgid "Enter your live secret key, found in your Stripe Account Settings" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:74 +msgid "Billing Address Display" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:75 +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 "" + +#: src/Gateways/Stripe/Admin/Settings.php:80 +msgid "Full address" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:81 +msgid "Zip / Postal Code and Country only" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:82 +msgid "No address fields" +msgstr "" + +#. translators: 1: opening link tag (do not translate), 2: closing link tag (do not translate) +#: src/Gateways/Stripe/Admin/Settings.php:90 +msgid "You can change the description of charges on a customer's bank statement in your %1$sStripe Settings%2$s." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:102 +msgid "Include Purchase Summary" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:103 +msgid "Include the product name(s) purchased in the payment descriptor for card payments. If the product name(s) are too long they will be shortened automatically." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:104 +msgid "Note: This setting does not affect non-card payment methods. Non-card payment methods will always use the Statement Descriptor above." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:110 +msgid "Shortened Descriptor" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:111 +msgid "When including the purchase summary in the payment descriptor for card payments, Stripe will use this shortened description as a prefix to the purchase summary." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:120 +msgid "Additional Settings" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:125 +msgid "Restrict Stripe Assets" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:126 +msgid "Only load Stripe.com hosted assets on pages that specifically utilize Stripe functionality." +msgstr "" + +#. translators: 1: opening link tag, 2: closing link tag +#: src/Gateways/Stripe/Admin/Settings.php:130 +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 "" + +#: src/Gateways/Stripe/Admin/Settings.php:147 +msgid "Elements Mode" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:148 +msgid "Toggle between using the legacy Card Elements Stripe integration and the new Payment Elements experience." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:151 +msgid "Card Element" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:152 +msgid "Payment Element" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:155 +msgid "Transitioning to Payment Elements" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:156 +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 "" + +#: src/Gateways/Stripe/Admin/Settings.php:162 +#: src/Gateways/Stripe/Admin/Settings.php:175 +msgid "Prepaid Cards" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:163 +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. +#: src/Gateways/Stripe/Admin/Settings.php:178 +msgid "Prepaid card allowance can now be managed in your %1$sStripe Radar Rules%2$s." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:188 +msgid "Split Credit Card Form" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:189 +msgid "Use separate card number, expiration, and CVC fields in payment forms." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:196 +#: src/Gateways/Stripe/Admin/Settings.php:204 +msgid "Show Previously Used Cards" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:197 +msgid "Provides logged in customers with a list of previously used payment methods for faster checkout." +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: src/Gateways/Stripe/Admin/Settings.php:207 +msgid "Previously used cards are now managed by %1$sLink by Stripe%2$s, for even better conversions and security." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:240 +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 "" + +#: src/Gateways/Stripe/Admin/Settings.php:265 +msgid "Unable to retrieve payment method configuration." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:271 +msgid "No payment methods available." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:286 +msgid "The methods actually displayed to your customers will vary based on multiple factors, such as currency, country, and what's in their cart." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:290 +msgid "Changes you make here will update your payment methods in Stripe's test mode. When you disable EDD's Test Mode, please revisit these settings." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:300 +msgid "Trials" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:333 +#: src/Gateways/Stripe/Admin/Settings.php:343 +msgid "Payment Methods Style" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:336 +msgid "Tabs" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:337 +msgid "Accordion" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:339 +msgid "Select the layout style for the Payment Methods section on the checkout form." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:344 +msgid "The Payment Methods Style setting is being overridden by a third-party plugin or custom code." +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:368 +msgid "Debugging Settings" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:370 +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 "" + +#: src/Gateways/Stripe/Admin/Settings.php:371 +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 "" + +#: src/Gateways/Stripe/Admin/Settings.php:378 +msgid "Enable access to Card Elements" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:379 +msgid "Access to Legacy Card Elements is Disabled" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:383 +msgid "Disable access to Card Elements" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:384 +msgid "Access to Legacy Card Elements is Enabled" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:390 +msgid "Toggle Card Elements" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:406 +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 "" + +#: src/Gateways/Stripe/Admin/Settings.php:495 +msgid "One-time payments" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:497 +msgid "subscriptions" +msgstr "" + +#: src/Gateways/Stripe/Admin/Settings.php:500 +msgid "subscriptions with trials" +msgstr "" + +#. translators: 1: opening strong tag, 2: closing strong tag, 3: payment types +#: src/Gateways/Stripe/Admin/Settings.php:505 +msgid "%1$sPayment types:%2$s %3$s" +msgstr "" + +#. translators: 1: opening strong tag, 2: closing strong tag, 3: supported currencies +#: src/Gateways/Stripe/Admin/Settings.php:514 +msgid "%1$sSupported currencies:%2$s %3$s" +msgstr "" + +#. translators: 1: opening strong tag, 2: closing strong tag, 3: supported countries +#: src/Gateways/Stripe/Admin/Settings.php:523 +msgid "%1$sSupported countries:%2$s %3$s" +msgstr "" + +#. translators: 1: opening strong tag, 2: closing strong tag +#: src/Gateways/Stripe/Admin/Settings.php:538 +msgid "%1$sStatus:%2$s You can test this, but to use it in Live mode, you must request access to this payment method from your Stripe account." +msgstr "" + +#. translators: 1: opening strong tag, 2: closing strong tag +#: src/Gateways/Stripe/Admin/Settings.php:544 +msgid "%1$sStatus:%2$s You must request access to this payment method from your Stripe account." +msgstr "" + +#. translators: 1: opening strong tag, 2: closing strong tag, 3: the message explaining the application fee (eg "3% per-transaction fee + Stripe fees"). +#: src/Gateways/Stripe/ApplicationFee.php:106 +msgid "%1$sPay as you go pricing:%2$s %3$s." +msgstr "" + +#. translators: Replacements are for the html wrappers for the phrse Upgrade to Pro and should not be translated. +#: src/Gateways/Stripe/ApplicationFee.php:114 +msgid "%1$sUpgrade to Pro%2$s to remove transaction fees." +msgstr "" + +#. translators: the application fee percentage. +#: src/Gateways/Stripe/ApplicationFee.php:178 +msgid "%d%% per-transaction fee + Stripe fees" +msgstr "" + +#. translators: the message explaining the application fee (eg "3% per-transaction fee + Stripe fees") +#: src/Gateways/Stripe/ApplicationFee.php:217 +msgid "Connect with Stripe for pay as you go pricing: %s." +msgstr "" + +#. translators: the message explaining the application fee (eg "3% per-transaction fee + Stripe fees") +#: src/Gateways/Stripe/ApplicationFee.php:225 +msgid "Connect with Stripe for pay as you go pricing: %s. Activate your license to remove the per-transaction fee." +msgstr "" + +#. translators: the date the grace period ends +#: src/Gateways/Stripe/ApplicationFee.php:245 +msgid "You are in a grace period for your new license. Activate your license by %s to prevent additional fees." +msgstr "" + +#. translators: 1: opening link tag, do not translate, 2: closing link tag, do not translate; 3. the date the grace period ends +#: src/Gateways/Stripe/ApplicationFee.php:256 +msgid "Your license has expired, but you are in a grace period. %1$sRenew your license key%2$s before %3$s to prevent being charged additional transaction fees." +msgstr "" + +#. translators: 1: the date the license expired, 2: opening link tag, do not translate, 3: closing link tag, do not translate +#: src/Gateways/Stripe/ApplicationFee.php:265 +msgid "Your license expired on %1$s. %2$sRenew your license%3$s to prevent additional fees." +msgstr "" + +#. translators: opening link tag, do not translate; closing link tag, do not translate +#: src/Gateways/Stripe/ApplicationFee.php:274 +msgid "%1$sActivate or upgrade your license%2$s to prevent additional fees." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Complete.php:39 +msgid "Error 1001: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Complete.php:50 +msgid "Error 1002: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Complete.php:176 +msgid "Error 1007: An error occurred completing the order, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Complete.php:238 +msgid "Error 1003: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Complete.php:262 +msgid "Error 1005: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Form.php:91 +msgid "Error 1008: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Form.php:214 +msgid "Stripe Error 002" +msgstr "" + +#. translators: %s: Error message +#: src/Gateways/Stripe/Checkout/Form.php:217 +msgid "There was an error while processing a Stripe payment. Order data: %s" +msgstr "" + +#: src/Gateways/Stripe/Checkout/Form.php:235 +msgid "Stripe Error 003" +msgstr "" + +#: src/Gateways/Stripe/Checkout/Form.php:482 +msgid "Error 1009: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Order.php:52 +msgid "Error 1006: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/Checkout/Order.php:192 +msgid "Error 1004: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/AchDebit.php:50 +msgid "ACH Direct Debit" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/AcssDebit.php:42 +msgid "Canadian Debit" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Affirm.php:50 +msgid "Affirm" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Alipay.php:89 +msgid "Alipay" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/AmazonPay.php:66 +msgid "Amazon Pay" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/ApplePay.php:58 +msgid "Apple Pay" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/BacsDebit.php:50 +msgid "Bacs Debit" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Bancontact.php:98 +msgid "Bancontact" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/CartesBancaires.php:50 +msgid "Cartes Bancaires" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Cashapp.php:74 +msgid "Cash App" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Eps.php:42 +msgid "EPS" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Fpx.php:50 +msgid "FPX" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Giropay.php:42 +msgid "giropay" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/GooglePay.php:58 +msgid "Google Pay" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Grabpay.php:53 +msgid "GrabPay" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Ideal.php:99 +msgid "iDEAL" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/P24.php:90 +msgid "Przelewy24" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/RevolutPay.php:58 +msgid "Revolut Pay" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/SepaDebit.php:91 +msgid "SEPA Direct Debit" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Sofort.php:42 +msgid "SOFORT" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/Twint.php:52 +msgid "TWINT" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/UsBankAccount.php:42 +msgid "US Bank Account" +msgstr "" + +#: src/Gateways/Stripe/PaymentMethods/WechatPay.php:34 +msgid "WeChat Pay" +msgstr "" + +#. translators: The charge ID from Stripe that is being refunded. +#: src/Gateways/Stripe/Webhooks/Events/ChargeRefunded.php:69 +msgid "Charge %s has been fully refunded in Stripe." +msgstr "" + +#. translators: The charge ID from Stripe that is being partially refunded. +#: src/Gateways/Stripe/Webhooks/Events/ChargeRefunded.php:73 +msgid "Charge %s partially refunded in Stripe." +msgstr "" + +#. translators: %s Stripe Radar early fraud warning reason. +#: src/Gateways/Stripe/Webhooks/Events/RadarEarlyFraudWarningCreated.php:49 +msgid "Stripe Radar early fraud warning created with a reason of %s." +msgstr "" + +#. translators: %s Stripe Radar review closing reason. +#: src/Gateways/Stripe/Webhooks/Events/ReviewClosed.php:61 +msgid "Stripe Radar review closed with a reason of %s." +msgstr "" + +#. translators: %s Stripe Radar review opening reason. +#: src/Gateways/Stripe/Webhooks/Events/ReviewOpened.php:68 +msgid "Stripe Radar review opened with a reason of %s." +msgstr "" + +#: src/Gateways/Stripe/Webhooks/Listener.php:119 +msgid "Invalid Event" +msgstr "" + +#. translators: %s: Download Category taxonomy name +#: src/HTML/CategorySelect.php:80 +msgctxt "plural: Example: \"Choose one or more Download Categories\"" +msgid "Choose %s" +msgstr "" + +#: src/HTML/Elements.php:113 +msgid "Choose a Customer" +msgstr "" + +#: src/HTML/Elements.php:117 +msgid "Search Customers" +msgstr "" + +#: src/HTML/Elements.php:119 +msgid "No customer attached" +msgstr "" + +#: src/HTML/Elements.php:186 +msgid "Select a User" +msgstr "" + +#: src/HTML/Elements.php:190 +msgid "Search Users" +msgstr "" + +#: src/HTML/Elements.php:266 +msgid "Choose a Discount" +msgstr "" + +#: src/HTML/Elements.php:267 +msgid "All Discounts" +msgstr "" + +#: src/HTML/Elements.php:452 +msgid "Choose a Country" +msgstr "" + +#: src/HTML/Elements.php:502 +msgid "Choose a Region" +msgstr "" + +#: src/HTML/Elements.php:623 +msgid "Enter Username" +msgstr "" + +#: src/HTML/Elements.php:654 +#: includes/blocks/build/register/index.js:1 +#: includes/blocks/src/register/edit.js:43 +#: includes/blocks/src/register/edit.js:52 +#: includes/blocks/src/register/edit.js:61 +#: includes/blocks/src/register/edit.js:70 +msgid "Required" +msgstr "" + +#. translators: %s: Download plural label +#: src/HTML/ProductSelect.php:87 +msgctxt "Noun: Download plural label for product select input" +msgid "Search %s" +msgstr "" + +#. translators: %s: Download plural label +#: src/HTML/ProductSelect.php:95 +msgctxt "Noun: Download plural label for selecting All Downloads" +msgid "All %s" +msgstr "" + +#: src/HTML/Select.php:117 +msgctxt "all dropdown items" +msgid "All" +msgstr "" + +#: src/HTML/Select.php:118 +msgctxt "no dropdown items" +msgid "None" +msgstr "" + +#. translators: %s: number of additional items that are not being displayed. +#: src/HTML/TimelineTooltip.php:134 +msgid "%s More" +msgstr "" + +#: src/HTML/Upload.php:47 +msgid "Attach File" +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 +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 "" + +#: src/Orders/Refunds/Validator.php:268 +msgid "No items have been selected to refund." +msgstr "" + +#. translators: %s: 0.00 formatted in store currency +#: src/Orders/Refunds/Validator.php:277 +msgid "The refund amount must be greater than %s." +msgstr "" + +#. translators: %s: maximum refund amount as formatted currency +#: src/Orders/Refunds/Validator.php:289 +msgid "The maximum refund amount is %s." +msgstr "" + +#. translators: %s: type of amount being refunded (e.g. "subtotal" or "tax"). Not translatable at this time. +#: src/Orders/Refunds/Validator.php:369 +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 +#: src/Orders/Refunds/Validator.php:398 +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 +#: src/Orders/Refunds/Validator.php:411 +msgid "The maximum refund %1$s for the adjustment \"%2$s\" is %3$s." +msgstr "" + +#: src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php:181 +msgid "Monthly Sales Average" +msgstr "" + +#: src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php:182 +msgid "Monthly Earnings Average" +msgstr "" + +#: src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php:272 +msgid "Due to the large number of products or terms on your site, this report may be slow or sometimes fail to load." +msgstr "" + +#: src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php:290 +msgid "Continue to Report" +msgstr "" + +#: src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php:297 +msgid "No taxonomies found." +msgstr "" + +#: src/Reports/Data/Gateways/StripePaymentMethods.php:80 +msgid "Refunded Sales" +msgstr "" + +#: src/RequirementsCheck.php:172 +msgid "This plugin is not fully active." +msgstr "" + +#. translators: 1: Requirement name, 2: Minimum version, 3: Current version +#: src/RequirementsCheck.php:183 +msgid "Requires %1$s (%2$s), but (%3$s) is installed." +msgstr "" + +#. translators: 1: Requirement name, 2: Minimum version +#: src/RequirementsCheck.php:194 +msgid "Requires %1$s (%2$s), but it appears to be missing." +msgstr "" + +#: src/RequirementsCheck.php:204 +msgid "Requirements" +msgstr "" + +#: src/RequirementsCheck.php:214 +msgid "Easy Digital Download Requirements" +msgstr "" + +#: src/Telemetry/Tracking.php:144 +msgid "Yes, I want to help!" +msgstr "" + +#: src/Telemetry/Tracking.php:275 +msgid "Allow" +msgstr "" + +#: src/Telemetry/Tracking.php:277 +msgid "Do not allow" +msgstr "" + +#: src/Telemetry/Tracking.php:290 +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 +#: src/Telemetry/Tracking.php:294 +msgid "%1$sHere is what we track.%2$s" +msgstr "" + +#: src/Upgrades/Adjustments/DiscountsStartEnd.php:205 +msgid "Discount Updates Complete!" +msgstr "" + +#: src/Upgrades/Adjustments/DiscountsStartEnd.php:206 +msgid "Easy Digital Downloads has finished updating your discount codes! Thank you for your patience." +msgstr "" + +#. translators: %s: % complete. +#: src/Upgrades/Adjustments/DiscountsStartEnd.php:234 +msgid "Updating Discounts ( %d%% )" +msgstr "" + +#: src/Upgrades/Adjustments/DiscountsStartEnd.php:257 +msgid "Easy Digital Downloads is performing maintenance in the background on discount codes that may contain invalid start and end dates. This process may take a while to complete depending on the number of discounts you have. We'll let you know when the process is complete." +msgstr "" + +#: src/Upgrades/Orders/MigrateAfterActionsDate.php:267 +msgid "Order Table Optimization Complete!" +msgstr "" + +#: src/Upgrades/Orders/MigrateAfterActionsDate.php:268 +msgid "Easy Digital Downloads has finished updating your orders database! Thank you for your patience." +msgstr "" + +#. translators: %s: % complete. +#: src/Upgrades/Orders/MigrateAfterActionsDate.php:296 +msgid "Optimizing Orders Table ( %d%% )" +msgstr "" + +#: src/Upgrades/Orders/MigrateAfterActionsDate.php:319 +msgid "Easy Digital Downloads is updating the Orders and Order Meta table in the background. This process may take a while to complete depending on the number of orders you have. We'll let you know when the process is complete." +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 "" + +#. translators: the order item's status. +#: templates/history-downloads.php:97 +msgctxt "The status of an order item" +msgid "Status: %s" +msgstr "" + +#: templates/history-downloads.php:135 +msgid "You have not purchased any downloads" +msgstr "" + +#: templates/history-purchases.php:79 +msgid "View Details and 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/stripe-success.php:13 +msgid "Thank you for your purchase. Your order is being processed and you will receive an email with your download link shortly." +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 "" + +#. translators: %s: Download label singular +#: includes/blocks/build/buy-button/index.js:3 +#: includes/blocks/build/buy-button/index.js:13 +#: includes/blocks/src/buy-button/edit.js:59 +#: includes/blocks/src/utilities/downloads.js:11 +msgid "Select a %s" +msgstr "" + +#. translators: %s: Download label singular +#: includes/blocks/build/buy-button/index.js:5 +#: includes/blocks/src/utilities/downloads.js:17 +msgid "Current %s" +msgstr "" + +#. translators: %s: Download label plural +#: includes/blocks/build/buy-button/index.js:6 +#: includes/blocks/build/downloads/index.js:2 +#: includes/blocks/src/utilities/download-new.js:12 +msgid "No Published %s Found" +msgstr "" + +#. translators: %s: Download label singular +#: includes/blocks/build/buy-button/index.js:8 +#: includes/blocks/build/downloads/index.js:4 +#: includes/blocks/src/utilities/download-new.js:23 +msgid "Create a New %s" +msgstr "" + +#. translators: %s: Download label singular +#: includes/blocks/build/buy-button/index.js:9 +#: includes/blocks/build/downloads/index.js:5 +#: includes/blocks/src/utilities/download-new.js:26 +msgid "Create Your First %s" +msgstr "" + +#. translators: %s: Download label singular +#: includes/blocks/build/buy-button/index.js:10 +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/utilities/download-new.js:37 +msgid "View All %s" +msgstr "" + +#. translators: %s: Download label singular +#: includes/blocks/build/buy-button/index.js:11 +#: includes/blocks/src/buy-button/edit.js:31 +msgid "Select a %s:" +msgstr "" + +#. translators: %s: Download label plural +#: includes/blocks/build/buy-button/index.js:12 +#: includes/blocks/src/buy-button/edit.js:35 +msgid "Published %s" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:13 +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/buy-button/edit.js:65 +#: includes/blocks/src/downloads/edit.js:158 +msgid "Show Price" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:13 +#: includes/blocks/src/buy-button/edit.js:74 +msgid "Enable Buy Now to process a download order without going through the full checkout." +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/src/cart/edit.js:30 +msgid "Mini Cart" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/cart/edit.js:36 +#: includes/blocks/src/utilities/download-order-by.js:18 +msgid "Title" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/src/cart/edit.js:42 +msgid "Hide When Empty" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/src/cart/edit.js:47 +msgid "Hide on Checkout" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/src/cart/edit.js:54 +msgid "Link Cart to Checkout" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/src/cart/edit.js:59 +msgid "Show Number of Items in Cart" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/src/cart/edit.js:64 +msgid "Show Cart Total" +msgstr "" + +#: includes/blocks/build/checkout/index.js:1 +#: includes/blocks/src/checkout/edit.js:20 +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 +#: includes/blocks/src/utilities/no-orders-placeholder.js:6 +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 +#: includes/blocks/src/confirmation/edit.js:29 +#: includes/blocks/src/receipt/edit.js:29 +msgid "Show Payment Key" +msgstr "" + +#: includes/blocks/build/confirmation/index.js:1 +#: includes/blocks/build/receipt/index.js:1 +#: includes/blocks/src/confirmation/edit.js:34 +#: includes/blocks/src/receipt/edit.js:34 +msgid "Show Gateway" +msgstr "" + +#: includes/blocks/build/confirmation/index.js:1 +#: includes/blocks/src/confirmation/edit.js:40 +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 +#: includes/blocks/src/utilities/buy-button-alignment.js:10 +#: includes/blocks/src/utilities/image-alignment.js:10 +msgid "Center" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +#: includes/blocks/src/utilities/buy-button-alignment.js:14 +#: includes/blocks/src/utilities/image-alignment.js:14 +msgid "Left" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +#: includes/blocks/src/utilities/buy-button-alignment.js:18 +#: includes/blocks/src/utilities/image-alignment.js:18 +msgid "Right" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +#: includes/blocks/src/utilities/order.js:6 +msgid "Ascending" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +#: includes/blocks/src/utilities/order.js:10 +msgid "Descending" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +#: includes/blocks/src/utilities/image-size.js:6 +msgid "Thumbnail" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +#: includes/blocks/src/utilities/image-size.js:10 +msgid "Medium" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +#: includes/blocks/src/utilities/image-size.js:14 +msgid "Large" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/buy-button-alignment.js:22 +msgid "Wide" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-order-by.js:6 +msgid "Date Published" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-order-by.js:30 +msgid "Date Modified" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-order-by.js:34 +msgid "Random" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-content.js:6 +msgid "No Content" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-content.js:10 +msgid "Full Content" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-image-location.js:6 +msgid "No Image" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-image-location.js:10 +msgid "Before Title" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/download-image-location.js:14 +msgid "After Title" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/src/utilities/users.js:7 +msgid "All authors" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:34 +#: includes/blocks/src/downloads/edit.js:35 +msgid "All Categories" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:41 +msgid "Product Block Settings" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:43 +msgid "Decide how to display your products." +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:45 +msgid "Downloads per Page" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:53 +msgid "Show All Access Downloads" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/downloads/edit.js:59 +#: includes/blocks/src/terms/edit.js:68 +msgid "Number of Columns" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/downloads/edit.js:66 +#: includes/blocks/src/terms/edit.js:56 +msgid "Order By" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:79 +msgid "Show Pagination" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:92 +msgid "Download Term Settings" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:98 +msgid "Show Downloads From Categories" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:106 +msgid "Show Downloads From Tags" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:114 +msgid "Individual Product Settings" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/downloads/edit.js:118 +#: includes/blocks/src/terms/edit.js:85 +msgid "Show Title" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:123 +msgid "Featured Image Location" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:130 +msgid "Should the featured image link to the product?" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:137 +msgid "Featured Image Size" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:145 +msgid "Featured Image Alignment" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:152 +msgid "Content" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:170 +msgid "Purchase Button Alignment" +msgstr "" + +#: includes/blocks/build/downloads/index.js:6 +#: includes/blocks/src/downloads/edit.js:176 +msgid "Show Price on Button" +msgstr "" + +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/src/login/edit.js:24 +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 +#: includes/blocks/src/login/edit.js:26 +#: includes/blocks/src/register/edit.js:25 +msgid "Redirect to Current Page" +msgstr "" + +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/register/index.js:1 +#: includes/blocks/src/login/edit.js:32 +#: includes/blocks/src/register/edit.js:31 +msgid "Custom Redirect URL" +msgstr "" + +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/src/login/edit.js:39 +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 +#: includes/blocks/src/order-history/edit.js:22 +msgid "This is an example of a user's order history." +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +#: includes/blocks/src/order-history/edit.js:24 +msgid "Order History Settings" +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +#: includes/blocks/src/order-history/edit.js:26 +msgid "Columns" +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +#: includes/blocks/src/order-history/edit.js:33 +msgid "Orders per Page" +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +#: includes/blocks/src/order-history/edit.js:41 +msgid "Do Not Show Renewal Orders" +msgstr "" + +#: includes/blocks/build/receipt/index.js:1 +#: includes/blocks/src/receipt/edit.js:40 +msgid "The editor will display a sample random order from your site." +msgstr "" + +#: includes/blocks/build/register/index.js:1 +#: includes/blocks/src/register/edit.js:23 +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 +#: includes/blocks/src/register/edit.js:38 +msgid "This form is a sample view of your registration form. Logged in users will not see it." +msgstr "" + +#. translators: %s: Download label singular +#: includes/blocks/build/terms/index.js:3 +#: includes/blocks/src/utilities/download-taxonomies.js:7 +msgid "%s Categories" +msgstr "" + +#. translators: %s: Download label singular +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/utilities/download-taxonomies.js:12 +msgid "%s Tags" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:27 +msgid "Count" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:47 +msgid "Term Block Settings" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:75 +msgid "Show Empty Categories" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:81 +msgid "Individual Term Settings" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:90 +msgid "Show Thumbnails" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:96 +msgid "Image Size" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:104 +msgid "Image Alignment" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:111 +msgid "Show Description" +msgstr "" + +#: includes/blocks/build/terms/index.js:5 +#: includes/blocks/src/terms/edit.js:116 +msgid "Show Count" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +#: includes/blocks/src/user-downloads/edit.js:23 +msgid "This is an example of a user's available downloads." +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +#: includes/blocks/src/user-downloads/edit.js:25 +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 +#: includes/blocks/src/user-downloads/edit.js:28 +msgid "User Download Settings" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +#: includes/blocks/src/user-downloads/edit.js:31 +#: includes/blocks/src/user-downloads/edit.js:38 +msgid "Show a Search Form" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +#: includes/blocks/src/user-downloads/edit.js:41 +msgid "This feature is available in EDD (Pro)." +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +#: includes/blocks/src/user-downloads/edit.js:45 +msgid "Show Product Variations" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +#: includes/blocks/src/user-downloads/edit.js:48 +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 +#: includes/blocks/src/user-downloads/edit.js:51 +msgid "Hide Products With No Files" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +#: includes/blocks/src/user-downloads/edit.js:57 +msgid "Text to show if there are no files" +msgstr "" + +#: includes/blocks/build/buy-button/block.json +#: includes/blocks/src/buy-button/block.json +msgctxt "block title" +msgid "EDD Buy Button" +msgstr "" + +#: includes/blocks/build/buy-button/block.json +#: includes/blocks/src/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/receipt/block.json +#: includes/blocks/build/register/block.json +#: includes/blocks/build/terms/block.json +#: includes/blocks/build/user-downloads/block.json +#: includes/blocks/src/buy-button/block.json +#: includes/blocks/src/cart/block.json +#: includes/blocks/src/checkout/block.json +#: includes/blocks/src/confirmation/block.json +#: includes/blocks/src/downloads/block.json +#: includes/blocks/src/login/block.json +#: includes/blocks/src/order-history/block.json +#: includes/blocks/src/receipt/block.json +#: includes/blocks/src/register/block.json +#: includes/blocks/src/terms/block.json +#: includes/blocks/src/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/receipt/block.json +#: includes/blocks/build/register/block.json +#: includes/blocks/build/terms/block.json +#: includes/blocks/build/user-downloads/block.json +#: includes/blocks/src/buy-button/block.json +#: includes/blocks/src/cart/block.json +#: includes/blocks/src/checkout/block.json +#: includes/blocks/src/confirmation/block.json +#: includes/blocks/src/downloads/block.json +#: includes/blocks/src/login/block.json +#: includes/blocks/src/order-history/block.json +#: includes/blocks/src/receipt/block.json +#: includes/blocks/src/register/block.json +#: includes/blocks/src/terms/block.json +#: includes/blocks/src/user-downloads/block.json +msgctxt "block keyword" +msgid "edd" +msgstr "" + +#: includes/blocks/build/buy-button/block.json +#: includes/blocks/src/buy-button/block.json +msgctxt "block keyword" +msgid "button" +msgstr "" + +#: includes/blocks/build/cart/block.json +#: includes/blocks/src/cart/block.json +msgctxt "block title" +msgid "EDD Cart" +msgstr "" + +#: includes/blocks/build/cart/block.json +#: includes/blocks/src/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 +#: includes/blocks/src/cart/block.json +msgctxt "block keyword" +msgid "cart" +msgstr "" + +#: includes/blocks/build/checkout/block.json +#: includes/blocks/src/checkout/block.json +msgctxt "block title" +msgid "EDD Checkout" +msgstr "" + +#: includes/blocks/build/checkout/block.json +#: includes/blocks/src/checkout/block.json +msgctxt "block description" +msgid "Full checkout block for Easy Digital Downloads." +msgstr "" + +#: includes/blocks/build/checkout/block.json +#: includes/blocks/src/checkout/block.json +msgctxt "block keyword" +msgid "checkout" +msgstr "" + +#: includes/blocks/build/confirmation/block.json +#: includes/blocks/src/confirmation/block.json +msgctxt "block title" +msgid "EDD Confirmation" +msgstr "" + +#: includes/blocks/build/confirmation/block.json +#: includes/blocks/src/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 +#: includes/blocks/src/confirmation/block.json +#: includes/blocks/src/order-history/block.json +#: includes/blocks/src/receipt/block.json +#: includes/blocks/src/user-downloads/block.json +msgctxt "block keyword" +msgid "orders" +msgstr "" + +#: includes/blocks/build/downloads/block.json +#: includes/blocks/src/downloads/block.json +msgctxt "block title" +msgid "EDD Products" +msgstr "" + +#: includes/blocks/build/downloads/block.json +#: includes/blocks/src/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 +#: includes/blocks/src/downloads/block.json +#: includes/blocks/src/terms/block.json +msgctxt "block keyword" +msgid "downloads" +msgstr "" + +#: includes/blocks/build/login/block.json +#: includes/blocks/src/login/block.json +msgctxt "block title" +msgid "EDD Login Form" +msgstr "" + +#: includes/blocks/build/login/block.json +#: includes/blocks/src/login/block.json +msgctxt "block description" +msgid "Login form for Easy Digital Downloads." +msgstr "" + +#: includes/blocks/build/login/block.json +#: includes/blocks/src/login/block.json +msgctxt "block keyword" +msgid "login" +msgstr "" + +#: includes/blocks/build/order-history/block.json +#: includes/blocks/src/order-history/block.json +msgctxt "block title" +msgid "EDD Order History" +msgstr "" + +#: includes/blocks/build/order-history/block.json +#: includes/blocks/src/order-history/block.json +msgctxt "block description" +msgid "Display the Easy Digital Downloads order history of a logged in user." +msgstr "" + +#: includes/blocks/build/receipt/block.json +#: includes/blocks/src/receipt/block.json +msgctxt "block title" +msgid "EDD Receipt" +msgstr "" + +#: includes/blocks/build/receipt/block.json +#: includes/blocks/src/receipt/block.json +msgctxt "block description" +msgid "Show customers their detailed receipt. Supports guest orders." +msgstr "" + +#: includes/blocks/build/register/block.json +#: includes/blocks/src/register/block.json +msgctxt "block title" +msgid "EDD Registration Form" +msgstr "" + +#: includes/blocks/build/register/block.json +#: includes/blocks/src/register/block.json +msgctxt "block description" +msgid "Registration form for Easy Digital Downloads." +msgstr "" + +#: includes/blocks/build/register/block.json +#: includes/blocks/src/register/block.json +msgctxt "block keyword" +msgid "registration" +msgstr "" + +#: includes/blocks/build/terms/block.json +#: includes/blocks/src/terms/block.json +msgctxt "block title" +msgid "EDD Download Terms" +msgstr "" + +#: includes/blocks/build/terms/block.json +#: includes/blocks/src/terms/block.json +msgctxt "block description" +msgid "Show categories, or tags for Easy Digital Download products." +msgstr "" + +#: includes/blocks/build/user-downloads/block.json +#: includes/blocks/src/user-downloads/block.json +msgctxt "block title" +msgid "EDD User Downloads" +msgstr "" + +#: includes/blocks/build/user-downloads/block.json +#: includes/blocks/src/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 @@ + + */ + protected function getCarbonClassName(): string + { + return Carbon::class; + } + + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + { + $precision = min( + $fieldDeclaration['precision'] ?? DateTimeDefaultPrecision::get(), + $this->getMaximumPrecision($platform), + ); + + $type = parent::getSQLDeclaration($fieldDeclaration, $platform); + + if (!$precision) { + return $type; + } + + if (str_contains($type, '(')) { + return preg_replace('/\(\d+\)/', "($precision)", $type); + } + + [$before, $after] = explode(' ', "$type "); + + return trim("$before($precision) $after"); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return $value->format('Y-m-d H:i:s.u'); + } + + throw InvalidType::new( + $value, + static::class, + ['null', 'DateTime', 'Carbon'] + ); + } + + private function doConvertToPHPValue(mixed $value) + { + $class = $this->getCarbonClassName(); + + if ($value === null || is_a($value, $class)) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return $class::instance($value); + } + + $date = null; + $error = null; + + try { + $date = $class::parse($value); + } catch (Exception $exception) { + $error = $exception; + } + + if (!$date) { + throw ValueNotConvertible::new( + $value, + static::class, + 'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()', + $error + ); + } + + return $date; + } + + private function getMaximumPrecision(AbstractPlatform $platform): int + { + if ($platform instanceof DB2Platform) { + return 12; + } + + if ($platform instanceof OraclePlatform) { + return 9; + } + + if ($platform instanceof SQLServerPlatform || $platform instanceof SQLitePlatform) { + return 3; + } + + return 6; + } +} diff --git a/libraries/Carbon/Doctrine/DateTimeDefaultPrecision.php b/libraries/Carbon/Doctrine/DateTimeDefaultPrecision.php new file mode 100644 index 00000000000..505b1457aa9 --- /dev/null +++ b/libraries/Carbon/Doctrine/DateTimeDefaultPrecision.php @@ -0,0 +1,30 @@ + */ + use CarbonTypeConverter; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonImmutable + { + return $this->doConvertToPHPValue($value); + } + + /** + * @return class-string + */ + protected function getCarbonClassName(): string + { + return CarbonImmutable::class; + } +} diff --git a/libraries/Carbon/Doctrine/DateTimeType.php b/libraries/Carbon/Doctrine/DateTimeType.php new file mode 100644 index 00000000000..9d54093d3f6 --- /dev/null +++ b/libraries/Carbon/Doctrine/DateTimeType.php @@ -0,0 +1,24 @@ + */ + use CarbonTypeConverter; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?EDD\Vendor\Carbon + { + return $this->doConvertToPHPValue($value); + } +} diff --git a/libraries/Carbon/LICENSE b/libraries/Carbon/LICENSE new file mode 100644 index 00000000000..6de45ebf882 --- /dev/null +++ b/libraries/Carbon/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) Brian Nesbitt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libraries/Carbon/bin/carbon b/libraries/Carbon/bin/carbon new file mode 100644 index 00000000000..b53ab738580 --- /dev/null +++ b/libraries/Carbon/bin/carbon @@ -0,0 +1,23 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\MessageFormatter; + +use EDD\Vendor\Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +if (!class_exists(LazyMessageFormatter::class, false)) { + abstract class LazyMessageFormatter implements MessageFormatterInterface + { + public function format(string $message, string $locale, array $parameters = []): string + { + return $this->formatter->format( + $message, + $this->transformLocale($locale), + $parameters + ); + } + } +} diff --git a/libraries/Carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php b/libraries/Carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php new file mode 100644 index 00000000000..6eac5a29cf3 --- /dev/null +++ b/libraries/Carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\MessageFormatter; + +use EDD\Vendor\Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface; +use EDD\Vendor\Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +if (!class_exists(LazyMessageFormatter::class, false)) { + abstract class LazyMessageFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface + { + abstract protected function transformLocale(?string $locale): ?string; + + public function format($message, $locale, array $parameters = []) + { + return $this->formatter->format( + $message, + $this->transformLocale($locale), + $parameters + ); + } + + public function choiceFormat($message, $number, $locale, array $parameters = []) + { + return $this->formatter->choiceFormat($message, $number, $locale, $parameters); + } + } +} diff --git a/libraries/Carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php b/libraries/Carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php new file mode 100644 index 00000000000..6ed26f08206 --- /dev/null +++ b/libraries/Carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection; +use ReflectionMethod; + +if (!class_exists(AbstractReflectionMacro::class, false)) { + abstract class AbstractReflectionMacro extends AbstractMacro + { + /** + * {@inheritdoc} + */ + public function getReflection(): ?ReflectionMethod + { + if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) { + return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction); + } + + return $this->reflectionFunction instanceof ReflectionMethod + ? $this->reflectionFunction + : null; + } + } +} diff --git a/libraries/Carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php b/libraries/Carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php new file mode 100644 index 00000000000..62c9e2e59a7 --- /dev/null +++ b/libraries/Carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection; +use ReflectionMethod; + +if (!class_exists(AbstractReflectionMacro::class, false)) { + abstract class AbstractReflectionMacro extends AbstractMacro + { + /** + * {@inheritdoc} + */ + public function getReflection(): ?Reflection\Adapter\ReflectionMethod + { + if ($this->reflectionFunction instanceof Reflection\Adapter\ReflectionMethod) { + return $this->reflectionFunction; + } + + if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) { + return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction); + } + + return $this->reflectionFunction instanceof ReflectionMethod + ? new Reflection\Adapter\ReflectionMethod( + Reflection\ReflectionMethod::createFromName( + $this->reflectionFunction->getDeclaringClass()->getName(), + $this->reflectionFunction->getName() + ) + ) + : null; + } + } +} diff --git a/libraries/Carbon/lazy/Carbon/PHPStan/MacroStrongType.php b/libraries/Carbon/lazy/Carbon/PHPStan/MacroStrongType.php new file mode 100644 index 00000000000..c17ee86e6ff --- /dev/null +++ b/libraries/Carbon/lazy/Carbon/PHPStan/MacroStrongType.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\PHPStan; + +if (!class_exists(LazyMacro::class, false)) { + abstract class LazyMacro extends AbstractReflectionMacro + { + /** + * {@inheritdoc} + */ + public function getFileName(): ?string + { + $file = $this->reflectionFunction->getFileName(); + + return (($file ? realpath($file) : null) ?: $file) ?: null; + } + + /** + * {@inheritdoc} + */ + public function getStartLine(): ?int + { + return $this->reflectionFunction->getStartLine(); + } + + /** + * {@inheritdoc} + */ + public function getEndLine(): ?int + { + return $this->reflectionFunction->getEndLine(); + } + } +} diff --git a/libraries/Carbon/lazy/Carbon/PHPStan/MacroWeakType.php b/libraries/Carbon/lazy/Carbon/PHPStan/MacroWeakType.php new file mode 100644 index 00000000000..8651f5b7e8a --- /dev/null +++ b/libraries/Carbon/lazy/Carbon/PHPStan/MacroWeakType.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\PHPStan; + +if (!class_exists(LazyMacro::class, false)) { + abstract class LazyMacro extends AbstractReflectionMacro + { + /** + * {@inheritdoc} + * + * @return string|false + */ + public function getFileName() + { + $file = $this->reflectionFunction->getFileName(); + + return (($file ? realpath($file) : null) ?: $file) ?: null; + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function getStartLine() + { + return $this->reflectionFunction->getStartLine(); + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function getEndLine() + { + return $this->reflectionFunction->getEndLine(); + } + } +} diff --git a/libraries/Carbon/lazy/Carbon/TranslatorStrongType.php b/libraries/Carbon/lazy/Carbon/TranslatorStrongType.php new file mode 100644 index 00000000000..6a3b2f87b44 --- /dev/null +++ b/libraries/Carbon/lazy/Carbon/TranslatorStrongType.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogueInterface; + +if (!class_exists(LazyTranslator::class, false)) { + class LazyTranslator extends AbstractTranslator implements TranslatorStrongTypeInterface + { + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + return $this->translate($id, $parameters, $domain, $locale); + } + + public function getFromCatalogue(MessageCatalogueInterface $catalogue, string $id, string $domain = 'messages') + { + $messages = $this->getPrivateProperty($catalogue, 'messages'); + + if (isset($messages[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX][$id])) { + return $messages[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX][$id]; + } + + if (isset($messages[$domain][$id])) { + return $messages[$domain][$id]; + } + + $fallbackCatalogue = $this->getPrivateProperty($catalogue, 'fallbackCatalogue'); + + if ($fallbackCatalogue !== null) { + return $this->getFromCatalogue($fallbackCatalogue, $id, $domain); + } + + return $id; + } + + private function getPrivateProperty($instance, string $field) + { + return (function (string $field) { + return $this->$field; + })->call($instance, $field); + } + } +} diff --git a/libraries/Carbon/lazy/Carbon/TranslatorWeakType.php b/libraries/Carbon/lazy/Carbon/TranslatorWeakType.php new file mode 100644 index 00000000000..11fc093ca82 --- /dev/null +++ b/libraries/Carbon/lazy/Carbon/TranslatorWeakType.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +if (!class_exists(LazyTranslator::class, false)) { + class LazyTranslator extends AbstractTranslator + { + /** + * Returns the translation. + * + * @param string|null $id + * @param array $parameters + * @param string|null $domain + * @param string|null $locale + * + * @return string + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + return $this->translate($id, $parameters, $domain, $locale); + } + } +} diff --git a/libraries/Carbon/readme.md b/libraries/Carbon/readme.md new file mode 100644 index 00000000000..97ec8ce08d2 --- /dev/null +++ b/libraries/Carbon/readme.md @@ -0,0 +1,176 @@ +# Carbon + +[![Latest Stable Version](https://img.shields.io/packagist/v/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) +[![Total Downloads](https://img.shields.io/packagist/dt/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) +[![GitHub Actions](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fbriannesbitt%2FCarbon%2Fbadge&style=flat-square&label=Build&logo=none)](https://github.com/briannesbitt/Carbon/actions) +[![codecov.io](https://img.shields.io/codecov/c/github/briannesbitt/Carbon.svg?style=flat-square)](https://codecov.io/github/briannesbitt/Carbon?branch=master) +[![Tidelift](https://tidelift.com/badges/github/briannesbitt/Carbon)](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) + +An international PHP extension for DateTime. [https://carbon.nesbot.com](https://carbon.nesbot.com) + +```php +toDateTimeString()); +printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver')); //implicit __toString() +$tomorrow = Carbon::now()->addDay(); +$lastWeek = Carbon::now()->subWeek(); +$nextSummerOlympics = Carbon::createFromDate(2016)->addYears(4); + +$officialDate = Carbon::now()->toRfc2822String(); + +$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age; + +$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London'); + +$internetWillBlowUpOn = Carbon::create(2038, 01, 19, 3, 14, 7, 'GMT'); + +// Don't really want this to happen so mock now +Carbon::setTestNow(Carbon::createFromDate(2000, 1, 1)); + +// comparisons are always done in UTC +if (Carbon::now()->gte($internetWillBlowUpOn)) { + die(); +} + +// Phew! Return to normal behaviour +Carbon::setTestNow(); + +if (Carbon::now()->isWeekend()) { + echo 'Party!'; +} +// Over 200 languages (and over 500 regional variants) supported: +echo Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago' +echo Carbon::now()->subMinutes(2)->locale('zh_CN')->diffForHumans(); // '2分钟前' +echo Carbon::parse('2019-07-23 14:51')->isoFormat('LLLL'); // 'Tuesday, July 23, 2019 2:51 PM' +echo Carbon::parse('2019-07-23 14:51')->locale('fr_FR')->isoFormat('LLLL'); // 'mardi 23 juillet 2019 14:51' + +// ... but also does 'from now', 'after' and 'before' +// rolling up to seconds, minutes, hours, days, months, years + +$daysSinceEpoch = Carbon::createFromTimestamp(0)->diffInDays(); +``` + +[Get supported nesbot/carbon with the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) + +## Installation + +### With Composer + +``` +$ composer require nesbot/carbon +``` + +```json +{ + "require": { + "nesbot/carbon": "^2.16" + } +} +``` + +```php + + +### Translators + +[Thanks to people helping us to translate Carbon in so many languages](https://carbon.nesbot.com/contribute/translators/) + +### Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. + + +Онлайн казино +CasinoHex Canada +Probukmacher +Casino-portugal.pt +Игровые автоматы +Slots City +inkedin +Онлайн казино України +OnlineCasinosSpelen +Best non Gamstop sites in the UK +Real Money Pokies +Non GamStop Bookies UK +Онлайн Казино Украины +SSSTwitter +Non-GamStop Bets UK +Chudovo +UK Casino Gap +NZ Casino Deps +NonStopCasino.org +Migliori Siti Non AAMS +UK NonGamStopCasinos +SnapTik +Proxidize +IG Downloader +Blastup +Organic Social Boost +AzuraCast +Triplebyte +GitHub Sponsors +Salesforce + + +[[Become a sponsor via OpenCollective](https://opencollective.com/Carbon#sponsor)] + + + + + + +[[Become a sponsor via GitHub](https://github.com/sponsors/kylekatarnls)] + +### Backers + +Thank you to all our backers! 🙏 + + + +[[Become a backer](https://opencollective.com/Carbon#backer)] + +## Carbon for enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of ``Carbon`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/libraries/Carbon/sponsors.php b/libraries/Carbon/sponsors.php new file mode 100644 index 00000000000..a9e9e2b1467 --- /dev/null +++ b/libraries/Carbon/sponsors.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Carbon\CarbonImmutable; + +require_once __DIR__.'/vendor/autoload.php'; + +function getMaxHistoryMonthsByAmount($amount): int +{ + if ($amount >= 50) { + return 6; + } + + if ($amount >= 20) { + return 4; + } + + return 2; +} + +function getHtmlAttribute($rawValue): string +{ + return str_replace( + ['​', "\r"], + '', + trim(htmlspecialchars((string) $rawValue), "  \n\r\t\v\0"), + ); +} + +function getOpenCollectiveSponsors(): string +{ + $customSponsorImages = [ + // For consistency and equity among sponsors, as of now, we kindly ask our sponsors + // to provide an image having a width/height ratio between 1/1 and 2/1. + // By default, we'll show the member picture from OpenCollective, and will resize it if bigger + // int(OpenCollective.MemberId) => ImageURL + ]; + + $members = json_decode(file_get_contents('https://opencollective.com/carbon/members/all.json'), true); + + $list = array_filter($members, static function ($member): bool { + return ($member['lastTransactionAmount'] > 3 || $member['isActive']) && + $member['role'] === 'BACKER' && + $member['type'] !== 'USER' && + ( + $member['totalAmountDonated'] > 100 || + $member['lastTransactionAt'] > CarbonImmutable::now() + ->subMonthsNoOverflow(getMaxHistoryMonthsByAmount($member['lastTransactionAmount'])) + ->format('Y-m-d h:i') || + $member['isActive'] && $member['lastTransactionAmount'] >= 30 + ); + }); + + $list = array_map(static function (array $member): array { + $createdAt = CarbonImmutable::parse($member['createdAt']); + $lastTransactionAt = CarbonImmutable::parse($member['lastTransactionAt']); + + if ($createdAt->format('d H:i:s.u') > $lastTransactionAt->format('d H:i:s.u')) { + $createdAt = $createdAt + ->setDay($lastTransactionAt->day) + ->modify($lastTransactionAt->format('H:i:s.u')); + } + + $monthlyContribution = (float) ($member['totalAmountDonated'] / ceil($createdAt->floatDiffInMonths())); + + if ( + $lastTransactionAt->isAfter('last month') && + $member['lastTransactionAmount'] > $monthlyContribution + ) { + $monthlyContribution = (float) $member['lastTransactionAmount']; + } + + $yearlyContribution = (float) ($member['totalAmountDonated'] / max(1, $createdAt->floatDiffInYears())); + $status = null; + + if ($monthlyContribution > 29) { + $status = 'sponsor'; + } elseif ($monthlyContribution > 4.5 || $yearlyContribution > 29) { + $status = 'backer'; + } elseif ($member['totalAmountDonated'] > 0) { + $status = 'helper'; + } + + return array_merge($member, [ + 'star' => ($monthlyContribution > 98 || $yearlyContribution > 500), + 'status' => $status, + 'monthlyContribution' => $monthlyContribution, + 'yearlyContribution' => $yearlyContribution, + ]); + }, $list); + + usort($list, static function (array $a, array $b): int { + return ($b['monthlyContribution'] <=> $a['monthlyContribution']) + ?: ($b['totalAmountDonated'] <=> $a['totalAmountDonated']); + }); + + return implode('', array_map(static function (array $member) use ($customSponsorImages): string { + $href = htmlspecialchars($member['website'] ?? $member['profile']); + $src = $customSponsorImages[$member['MemberId'] ?? ''] ?? $member['image'] ?? (strtr($member['profile'], ['https://opencollective.com/' => 'https://images.opencollective.com/']).'/avatar/256.png'); + [$x, $y] = @getimagesize($src) ?: [0, 0]; + $validImage = ($x && $y); + $src = $validImage ? htmlspecialchars($src) : 'https://opencollective.com/static/images/default-guest-logo.svg'; + $height = $member['status'] === 'sponsor' ? 64 : 42; + $width = min($height * 2, $validImage ? round($x * $height / $y) : $height); + $href .= (strpos($href, '?') === false ? '?' : '&').'utm_source=opencollective&utm_medium=github&utm_campaign=Carbon'; + $title = getHtmlAttribute(($member['description'] ?? null) ?: $member['name']); + $alt = getHtmlAttribute($member['name']); + + return "\n".''. + ''.$alt.''. + ''; + }, $list))."\n"; +} + +file_put_contents('readme.md', preg_replace_callback( + '/()[\s\S]+()/', + static function (array $match): string { + return $match[1].getOpenCollectiveSponsors().$match[2]; + }, + file_get_contents('readme.md') +)); diff --git a/libraries/Carbon/src/Carbon/AbstractTranslator.php b/libraries/Carbon/src/Carbon/AbstractTranslator.php new file mode 100644 index 00000000000..7bc43fc5a05 --- /dev/null +++ b/libraries/Carbon/src/Carbon/AbstractTranslator.php @@ -0,0 +1,398 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Carbon\MessageFormatter\MessageFormatterMapper; +use Closure; +use ReflectionException; +use ReflectionFunction; +use EDD\Vendor\Symfony\Component\Translation; +use EDD\Vendor\Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use EDD\Vendor\Symfony\Component\Translation\Loader\ArrayLoader; + +abstract class AbstractTranslator extends Translation\Translator +{ + /** + * Translator singletons for each language. + * + * @var array + */ + protected static $singletons = []; + + /** + * List of custom localized messages. + * + * @var array + */ + protected $messages = []; + + /** + * List of custom directories that contain translation files. + * + * @var string[] + */ + protected $directories = []; + + /** + * Set to true while constructing. + * + * @var bool + */ + protected $initializing = false; + + /** + * List of locales aliases. + * + * @var array + */ + protected $aliases = [ + 'me' => 'sr_Latn_ME', + 'scr' => 'sh', + ]; + + /** + * Return a singleton instance of Translator. + * + * @param string|null $locale optional initial locale ("en" - english by default) + * + * @return static + */ + public static function get($locale = null) + { + $locale = $locale ?: 'en'; + $key = static::class === Translator::class ? $locale : static::class.'|'.$locale; + + if (!isset(static::$singletons[$key])) { + static::$singletons[$key] = new static($locale); + } + + return static::$singletons[$key]; + } + + public function __construct($locale, MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) + { + parent::setLocale($locale); + $this->initializing = true; + $this->directories = [__DIR__.'/Lang']; + $this->addLoader('array', new ArrayLoader()); + parent::__construct($locale, new MessageFormatterMapper($formatter), $cacheDir, $debug); + $this->initializing = false; + } + + /** + * Returns the list of directories translation files are searched in. + * + * @return array + */ + public function getDirectories(): array + { + return $this->directories; + } + + /** + * Set list of directories translation files are searched in. + * + * @param array $directories new directories list + * + * @return $this + */ + public function setDirectories(array $directories) + { + $this->directories = $directories; + + return $this; + } + + /** + * Add a directory to the list translation files are searched in. + * + * @param string $directory new directory + * + * @return $this + */ + public function addDirectory(string $directory) + { + $this->directories[] = $directory; + + return $this; + } + + /** + * Remove a directory from the list translation files are searched in. + * + * @param string $directory directory path + * + * @return $this + */ + public function removeDirectory(string $directory) + { + $search = rtrim(strtr($directory, '\\', '/'), '/'); + + return $this->setDirectories(array_filter($this->getDirectories(), function ($item) use ($search) { + return rtrim(strtr($item, '\\', '/'), '/') !== $search; + })); + } + + /** + * Reset messages of a locale (all locale if no locale passed). + * Remove custom messages and reload initial messages from matching + * file in Lang directory. + * + * @param string|null $locale + * + * @return bool + */ + public function resetMessages($locale = null) + { + if ($locale === null) { + $this->messages = []; + + return true; + } + + foreach ($this->getDirectories() as $directory) { + $data = @include sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale); + + if ($data !== false) { + $this->messages[$locale] = $data; + $this->addResource('array', $this->messages[$locale], $locale); + + return true; + } + } + + return false; + } + + /** + * Returns the list of files matching a given locale prefix (or all if empty). + * + * @param string $prefix prefix required to filter result + * + * @return array + */ + public function getLocalesFiles($prefix = '') + { + $files = []; + + foreach ($this->getDirectories() as $directory) { + $directory = rtrim($directory, '\\/'); + + foreach (glob("$directory/$prefix*.php") as $file) { + $files[] = $file; + } + } + + return array_unique($files); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @param string $prefix prefix required to filter result + * + * @return array + */ + public function getAvailableLocales($prefix = '') + { + $locales = []; + foreach ($this->getLocalesFiles($prefix) as $file) { + $locales[] = substr($file, strrpos($file, '/') + 1, -4); + } + + return array_unique(array_merge($locales, array_keys($this->messages))); + } + + protected function translate(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + if ($domain === null) { + $domain = 'messages'; + } + + $catalogue = $this->getCatalogue($locale); + $format = $this instanceof TranslatorStrongTypeInterface + ? $this->getFromCatalogue($catalogue, (string) $id, $domain) + : $this->getCatalogue($locale)->get((string) $id, $domain); // @codeCoverageIgnore + + if ($format instanceof Closure) { + // @codeCoverageIgnoreStart + try { + $count = (new ReflectionFunction($format))->getNumberOfRequiredParameters(); + } catch (ReflectionException $exception) { + $count = 0; + } + // @codeCoverageIgnoreEnd + + return $format( + ...array_values($parameters), + ...array_fill(0, max(0, $count - \count($parameters)), null) + ); + } + + return parent::trans($id, $parameters, $domain, $locale); + } + + /** + * Init messages language from matching file in Lang directory. + * + * @param string $locale + * + * @return bool + */ + protected function loadMessagesFromFile($locale) + { + return isset($this->messages[$locale]) || $this->resetMessages($locale); + } + + /** + * Set messages of a locale and take file first if present. + * + * @param string $locale + * @param array $messages + * + * @return $this + */ + public function setMessages($locale, $messages) + { + $this->loadMessagesFromFile($locale); + $this->addResource('array', $messages, $locale); + $this->messages[$locale] = array_merge( + $this->messages[$locale] ?? [], + $messages + ); + + return $this; + } + + /** + * Set messages of the current locale and take file first if present. + * + * @param array $messages + * + * @return $this + */ + public function setTranslations($messages) + { + return $this->setMessages($this->getLocale(), $messages); + } + + /** + * Get messages of a locale, if none given, return all the + * languages. + * + * @param string|null $locale + * + * @return array + */ + public function getMessages($locale = null) + { + return $locale === null ? $this->messages : $this->messages[$locale]; + } + + /** + * Set the current translator locale and indicate if the source locale file exists + * + * @param string $locale locale ex. en + * + * @return bool + */ + public function setLocale($locale) + { + $locale = preg_replace_callback('/[-_]([a-z]{2,}|\d{2,})/', function ($matches) { + // _2-letters or YUE is a region, _3+-letters is a variant + $upper = strtoupper($matches[1]); + + if ($upper === 'YUE' || $upper === 'ISO' || \strlen($upper) < 3) { + return "_$upper"; + } + + return '_'.ucfirst($matches[1]); + }, strtolower($locale)); + + $previousLocale = $this->getLocale(); + + if ($previousLocale === $locale && isset($this->messages[$locale])) { + return true; + } + + unset(static::$singletons[$previousLocale]); + + if ($locale === 'auto') { + $completeLocale = setlocale(LC_TIME, '0'); + $locale = preg_replace('/^([^_.-]+).*$/', '$1', $completeLocale); + $locales = $this->getAvailableLocales($locale); + + $completeLocaleChunks = preg_split('/[_.-]+/', $completeLocale); + + $getScore = function ($language) use ($completeLocaleChunks) { + return self::compareChunkLists($completeLocaleChunks, preg_split('/[_.-]+/', $language)); + }; + + usort($locales, function ($first, $second) use ($getScore) { + return $getScore($second) <=> $getScore($first); + }); + + $locale = $locales[0]; + } + + if (isset($this->aliases[$locale])) { + $locale = $this->aliases[$locale]; + } + + // If subtag (ex: en_CA) first load the macro (ex: en) to have a fallback + if (str_contains($locale, '_') && + $this->loadMessagesFromFile($macroLocale = preg_replace('/^([^_]+).*$/', '$1', $locale)) + ) { + parent::setLocale($macroLocale); + } + + if (!$this->loadMessagesFromFile($locale) && !$this->initializing) { + return false; + } + + parent::setLocale($locale); + + return true; + } + + /** + * Show locale on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + return [ + 'locale' => $this->getLocale(), + ]; + } + + private static function compareChunkLists($referenceChunks, $chunks) + { + $score = 0; + + foreach ($referenceChunks as $index => $chunk) { + if (!isset($chunks[$index])) { + $score++; + + continue; + } + + if (strtolower($chunks[$index]) === strtolower($chunk)) { + $score += 10; + } + } + + return $score; + } +} diff --git a/libraries/Carbon/src/Carbon/Carbon.php b/libraries/Carbon/src/Carbon/Carbon.php new file mode 100644 index 00000000000..e17f891b78c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Carbon.php @@ -0,0 +1,523 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Carbon\Traits\Date; +use EDD\Vendor\Carbon\Traits\DeprecatedProperties; +use DateTime; +use DateTimeInterface; +use DateTimeZone; + +/** + * A simple API extension for DateTime. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method $this years(int $value) Set current instance year to the given value. + * @method $this year(int $value) Set current instance year to the given value. + * @method $this setYears(int $value) Set current instance year to the given value. + * @method $this setYear(int $value) Set current instance year to the given value. + * @method $this months(int $value) Set current instance month to the given value. + * @method $this month(int $value) Set current instance month to the given value. + * @method $this setMonths(int $value) Set current instance month to the given value. + * @method $this setMonth(int $value) Set current instance month to the given value. + * @method $this days(int $value) Set current instance day to the given value. + * @method $this day(int $value) Set current instance day to the given value. + * @method $this setDays(int $value) Set current instance day to the given value. + * @method $this setDay(int $value) Set current instance day to the given value. + * @method $this hours(int $value) Set current instance hour to the given value. + * @method $this hour(int $value) Set current instance hour to the given value. + * @method $this setHours(int $value) Set current instance hour to the given value. + * @method $this setHour(int $value) Set current instance hour to the given value. + * @method $this minutes(int $value) Set current instance minute to the given value. + * @method $this minute(int $value) Set current instance minute to the given value. + * @method $this setMinutes(int $value) Set current instance minute to the given value. + * @method $this setMinute(int $value) Set current instance minute to the given value. + * @method $this seconds(int $value) Set current instance second to the given value. + * @method $this second(int $value) Set current instance second to the given value. + * @method $this setSeconds(int $value) Set current instance second to the given value. + * @method $this setSecond(int $value) Set current instance second to the given value. + * @method $this millis(int $value) Set current instance millisecond to the given value. + * @method $this milli(int $value) Set current instance millisecond to the given value. + * @method $this setMillis(int $value) Set current instance millisecond to the given value. + * @method $this setMilli(int $value) Set current instance millisecond to the given value. + * @method $this milliseconds(int $value) Set current instance millisecond to the given value. + * @method $this millisecond(int $value) Set current instance millisecond to the given value. + * @method $this setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method $this setMillisecond(int $value) Set current instance millisecond to the given value. + * @method $this micros(int $value) Set current instance microsecond to the given value. + * @method $this micro(int $value) Set current instance microsecond to the given value. + * @method $this setMicros(int $value) Set current instance microsecond to the given value. + * @method $this setMicro(int $value) Set current instance microsecond to the given value. + * @method $this microseconds(int $value) Set current instance microsecond to the given value. + * @method $this microsecond(int $value) Set current instance microsecond to the given value. + * @method $this setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method $this setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method $this addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method $this addYear() Add one year to the instance (using date interval). + * @method $this subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method $this subYear() Sub one year to the instance (using date interval). + * @method $this addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method $this addMonth() Add one month to the instance (using date interval). + * @method $this subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method $this subMonth() Sub one month to the instance (using date interval). + * @method $this addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method $this addDay() Add one day to the instance (using date interval). + * @method $this subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method $this subDay() Sub one day to the instance (using date interval). + * @method $this addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method $this addHour() Add one hour to the instance (using date interval). + * @method $this subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method $this subHour() Sub one hour to the instance (using date interval). + * @method $this addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method $this addMinute() Add one minute to the instance (using date interval). + * @method $this subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method $this subMinute() Sub one minute to the instance (using date interval). + * @method $this addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method $this addSecond() Add one second to the instance (using date interval). + * @method $this subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method $this subSecond() Sub one second to the instance (using date interval). + * @method $this addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMilli() Add one millisecond to the instance (using date interval). + * @method $this subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMilli() Sub one millisecond to the instance (using date interval). + * @method $this addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMillisecond() Add one millisecond to the instance (using date interval). + * @method $this subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMillisecond() Sub one millisecond to the instance (using date interval). + * @method $this addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicro() Add one microsecond to the instance (using date interval). + * @method $this subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicro() Sub one microsecond to the instance (using date interval). + * @method $this addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicrosecond() Add one microsecond to the instance (using date interval). + * @method $this subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method $this addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method $this addMillennium() Add one millennium to the instance (using date interval). + * @method $this subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method $this subMillennium() Sub one millennium to the instance (using date interval). + * @method $this addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method $this addCentury() Add one century to the instance (using date interval). + * @method $this subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method $this subCentury() Sub one century to the instance (using date interval). + * @method $this addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method $this addDecade() Add one decade to the instance (using date interval). + * @method $this subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method $this subDecade() Sub one decade to the instance (using date interval). + * @method $this addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method $this addQuarter() Add one quarter to the instance (using date interval). + * @method $this subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method $this subQuarter() Sub one quarter to the instance (using date interval). + * @method $this addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method $this addWeek() Add one week to the instance (using date interval). + * @method $this subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method $this subWeek() Sub one week to the instance (using date interval). + * @method $this addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method $this addWeekday() Add one weekday to the instance (using date interval). + * @method $this subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method $this subWeekday() Sub one weekday to the instance (using date interval). + * @method $this addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMicro() Add one microsecond to the instance (using timestamp). + * @method $this subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method $this addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method $this subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method $this addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMilli() Add one millisecond to the instance (using timestamp). + * @method $this subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method $this addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method $this subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method $this addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealSecond() Add one second to the instance (using timestamp). + * @method $this subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each second or every X seconds if a factor is given. + * @method $this addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMinute() Add one minute to the instance (using timestamp). + * @method $this subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each minute or every X minutes if a factor is given. + * @method $this addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method $this addRealHour() Add one hour to the instance (using timestamp). + * @method $this subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method $this subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each hour or every X hours if a factor is given. + * @method $this addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method $this addRealDay() Add one day to the instance (using timestamp). + * @method $this subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method $this subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each day or every X days if a factor is given. + * @method $this addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method $this addRealWeek() Add one week to the instance (using timestamp). + * @method $this subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method $this subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each week or every X weeks if a factor is given. + * @method $this addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMonth() Add one month to the instance (using timestamp). + * @method $this subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each month or every X months if a factor is given. + * @method $this addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method $this addRealQuarter() Add one quarter to the instance (using timestamp). + * @method $this subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method $this subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each quarter or every X quarters if a factor is given. + * @method $this addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method $this addRealYear() Add one year to the instance (using timestamp). + * @method $this subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method $this subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each year or every X years if a factor is given. + * @method $this addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method $this addRealDecade() Add one decade to the instance (using timestamp). + * @method $this subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method $this subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each decade or every X decades if a factor is given. + * @method $this addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method $this addRealCentury() Add one century to the instance (using timestamp). + * @method $this subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method $this subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each century or every X centuries if a factor is given. + * @method $this addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMillennium() Add one millennium to the instance (using timestamp). + * @method $this subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millennium or every X millennia if a factor is given. + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method static static|false createFromFormat(string $format, string $time, DateTimeZone|string|false|null $timezone = null) Parse a string into a new EDD\Vendor\Carbon object according to the specified format. + * @method static static __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * + * + */ +class Carbon extends DateTime implements CarbonInterface +{ + use Date; + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable() + { + return true; + } +} diff --git a/libraries/Carbon/src/Carbon/CarbonConverterInterface.php b/libraries/Carbon/src/Carbon/CarbonConverterInterface.php new file mode 100644 index 00000000000..901b0ba5cc1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/CarbonConverterInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use DateTimeInterface; + +interface CarbonConverterInterface +{ + public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface; +} diff --git a/libraries/Carbon/src/Carbon/CarbonImmutable.php b/libraries/Carbon/src/Carbon/CarbonImmutable.php new file mode 100644 index 00000000000..4221517064f --- /dev/null +++ b/libraries/Carbon/src/Carbon/CarbonImmutable.php @@ -0,0 +1,582 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Carbon\Traits\Date; +use EDD\Vendor\Carbon\Traits\DeprecatedProperties; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; + +/** + * A simple API extension for DateTimeImmutable. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonImmutable years(int $value) Set current instance year to the given value. + * @method CarbonImmutable year(int $value) Set current instance year to the given value. + * @method CarbonImmutable setYears(int $value) Set current instance year to the given value. + * @method CarbonImmutable setYear(int $value) Set current instance year to the given value. + * @method CarbonImmutable months(int $value) Set current instance month to the given value. + * @method CarbonImmutable month(int $value) Set current instance month to the given value. + * @method CarbonImmutable setMonths(int $value) Set current instance month to the given value. + * @method CarbonImmutable setMonth(int $value) Set current instance month to the given value. + * @method CarbonImmutable days(int $value) Set current instance day to the given value. + * @method CarbonImmutable day(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDays(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDay(int $value) Set current instance day to the given value. + * @method CarbonImmutable hours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable hour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable minutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable minute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable seconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable second(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSeconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSecond(int $value) Set current instance second to the given value. + * @method CarbonImmutable millis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable micros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable micro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addYear() Add one year to the instance (using date interval). + * @method CarbonImmutable subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subYear() Sub one year to the instance (using date interval). + * @method CarbonImmutable addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMonth() Add one month to the instance (using date interval). + * @method CarbonImmutable subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMonth() Sub one month to the instance (using date interval). + * @method CarbonImmutable addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDay() Add one day to the instance (using date interval). + * @method CarbonImmutable subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDay() Sub one day to the instance (using date interval). + * @method CarbonImmutable addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addHour() Add one hour to the instance (using date interval). + * @method CarbonImmutable subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subHour() Sub one hour to the instance (using date interval). + * @method CarbonImmutable addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMinute() Add one minute to the instance (using date interval). + * @method CarbonImmutable subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMinute() Sub one minute to the instance (using date interval). + * @method CarbonImmutable addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addSecond() Add one second to the instance (using date interval). + * @method CarbonImmutable subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subSecond() Sub one second to the instance (using date interval). + * @method CarbonImmutable addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonImmutable subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonImmutable addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addCentury() Add one century to the instance (using date interval). + * @method CarbonImmutable subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subCentury() Sub one century to the instance (using date interval). + * @method CarbonImmutable addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDecade() Add one decade to the instance (using date interval). + * @method CarbonImmutable subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDecade() Sub one decade to the instance (using date interval). + * @method CarbonImmutable addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonImmutable subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonImmutable addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeek() Add one week to the instance (using date interval). + * @method CarbonImmutable subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeek() Sub one week to the instance (using date interval). + * @method CarbonImmutable addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonImmutable subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonImmutable addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonImmutable addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonImmutable addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonImmutable addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonImmutable addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonImmutable subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonImmutable addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonImmutable subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonImmutable addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonImmutable subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonImmutable addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealDay() Add one day to the instance (using timestamp). + * @method CarbonImmutable subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each day or every X days if a factor is given. + * @method CarbonImmutable addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonImmutable subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonImmutable addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonImmutable subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each month or every X months if a factor is given. + * @method CarbonImmutable addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonImmutable subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonImmutable addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealYear() Add one year to the instance (using timestamp). + * @method CarbonImmutable subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each year or every X years if a factor is given. + * @method CarbonImmutable addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonImmutable subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonImmutable addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonImmutable subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonImmutable addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonImmutable subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonImmutable roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method static static|false createFromFormat(string $format, string $time, DateTimeZone|string|false|null $timezone = null) Parse a string into a new CarbonImmutable object according to the specified format. + * @method static static __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * + * + */ +class CarbonImmutable extends DateTimeImmutable implements CarbonInterface +{ + use Date { + __clone as dateTraitClone; + } + + public function __clone() + { + $this->dateTraitClone(); + $this->endOfTime = false; + $this->startOfTime = false; + } + + /** + * Create a very old date representing start of time. + * + * @return static + */ + public static function startOfTime(): self + { + $date = static::parse('0001-01-01')->years(self::getStartOfTimeYear()); + $date->startOfTime = true; + + return $date; + } + + /** + * Create a very far date representing end of time. + * + * @return static + */ + public static function endOfTime(): self + { + $date = static::parse('9999-12-31 23:59:59.999999')->years(self::getEndOfTimeYear()); + $date->endOfTime = true; + + return $date; + } + + /** + * @codeCoverageIgnore + */ + private static function getEndOfTimeYear(): int + { + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + return 145261681241552; + } + + // Remove if https://bugs.php.net/bug.php?id=81107 is fixed + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { + return 1118290769066902787; + } + + return PHP_INT_MAX; + } + + /** + * @codeCoverageIgnore + */ + private static function getStartOfTimeYear(): int + { + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + return -135908816449551; + } + + // Remove if https://bugs.php.net/bug.php?id=81107 is fixed + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { + return -1118290769066898816; + } + + return max(PHP_INT_MIN, -9223372036854773760); + } +} diff --git a/libraries/Carbon/src/Carbon/CarbonInterface.php b/libraries/Carbon/src/Carbon/CarbonInterface.php new file mode 100644 index 00000000000..f09e31e3844 --- /dev/null +++ b/libraries/Carbon/src/Carbon/CarbonInterface.php @@ -0,0 +1,5142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use BadMethodCallException; +use EDD\Vendor\Carbon\Exceptions\BadComparisonUnitException; +use EDD\Vendor\Carbon\Exceptions\ImmutableException; +use EDD\Vendor\Carbon\Exceptions\InvalidDateException; +use EDD\Vendor\Carbon\Exceptions\InvalidFormatException; +use EDD\Vendor\Carbon\Exceptions\UnknownGetterException; +use EDD\Vendor\Carbon\Exceptions\UnknownMethodException; +use EDD\Vendor\Carbon\Exceptions\UnknownSetterException; +use Closure; +use DateInterval; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use JsonSerializable; +use ReflectionException; +use ReturnTypeWillChange; +use EDD\Vendor\Symfony\Component\Translation\TranslatorInterface; +use Throwable; + +/** + * Common interface for EDD\Vendor\Carbon and CarbonImmutable. + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonInterface years(int $value) Set current instance year to the given value. + * @method CarbonInterface year(int $value) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value) Set current instance year to the given value. + * @method CarbonInterface months(int $value) Set current instance month to the given value. + * @method CarbonInterface month(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each day or every X days if a factor is given. + * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each month or every X months if a factor is given. + * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each year or every X years if a factor is given. + * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * + * + */ +interface CarbonInterface extends DateTimeInterface, JsonSerializable +{ + /** + * Diff wording options(expressed in octal). + */ + public const NO_ZERO_DIFF = 01; + public const JUST_NOW = 02; + public const ONE_DAY_WORDS = 04; + public const TWO_DAY_WORDS = 010; + public const SEQUENTIAL_PARTS_ONLY = 020; + public const ROUND = 040; + public const FLOOR = 0100; + public const CEIL = 0200; + + /** + * Diff syntax options. + */ + public const DIFF_ABSOLUTE = 1; // backward compatibility with true + public const DIFF_RELATIVE_AUTO = 0; // backward compatibility with false + public const DIFF_RELATIVE_TO_NOW = 2; + public const DIFF_RELATIVE_TO_OTHER = 3; + + /** + * Translate string options. + */ + public const TRANSLATE_MONTHS = 1; + public const TRANSLATE_DAYS = 2; + public const TRANSLATE_UNITS = 4; + public const TRANSLATE_MERIDIEM = 8; + public const TRANSLATE_DIFF = 0x10; + public const TRANSLATE_ALL = self::TRANSLATE_MONTHS | self::TRANSLATE_DAYS | self::TRANSLATE_UNITS | self::TRANSLATE_MERIDIEM | self::TRANSLATE_DIFF; + + /** + * The day constants. + */ + public const SUNDAY = 0; + public const MONDAY = 1; + public const TUESDAY = 2; + public const WEDNESDAY = 3; + public const THURSDAY = 4; + public const FRIDAY = 5; + public const SATURDAY = 6; + + /** + * The month constants. + * These aren't used by EDD\Vendor\Carbon itself but exist for + * convenience sake alone. + */ + public const JANUARY = 1; + public const FEBRUARY = 2; + public const MARCH = 3; + public const APRIL = 4; + public const MAY = 5; + public const JUNE = 6; + public const JULY = 7; + public const AUGUST = 8; + public const SEPTEMBER = 9; + public const OCTOBER = 10; + public const NOVEMBER = 11; + public const DECEMBER = 12; + + /** + * Number of X in Y. + */ + public const YEARS_PER_MILLENNIUM = 1000; + public const YEARS_PER_CENTURY = 100; + public const YEARS_PER_DECADE = 10; + public const MONTHS_PER_YEAR = 12; + public const MONTHS_PER_QUARTER = 3; + public const QUARTERS_PER_YEAR = 4; + public const WEEKS_PER_YEAR = 52; + public const WEEKS_PER_MONTH = 4; + public const DAYS_PER_YEAR = 365; + public const DAYS_PER_WEEK = 7; + public const HOURS_PER_DAY = 24; + public const MINUTES_PER_HOUR = 60; + public const SECONDS_PER_MINUTE = 60; + public const MILLISECONDS_PER_SECOND = 1000; + public const MICROSECONDS_PER_MILLISECOND = 1000; + public const MICROSECONDS_PER_SECOND = 1000000; + + /** + * Special settings to get the start of week from current locale culture. + */ + public const WEEK_DAY_AUTO = 'auto'; + + /** + * RFC7231 DateTime format. + * + * @var string + */ + public const RFC7231_FORMAT = 'D, d M Y H:i:s \G\M\T'; + + /** + * Default format to use for __toString method when type juggling occurs. + * + * @var string + */ + public const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; + + /** + * Format for converting mocked time, includes microseconds. + * + * @var string + */ + public const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; + + /** + * Pattern detection for ->isoFormat and ::createFromIsoFormat. + * + * @var string + */ + public const ISO_FORMAT_REGEXP = '(O[YMDHhms]|[Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY?|g{1,5}|G{1,5}|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?)'; + + // + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable + * + * @return mixed + */ + public function __call($method, $parameters); + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters); + + /** + * Update constructedObjectId on cloned. + */ + public function __clone(); + + /** + * Create a new EDD\Vendor\Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param DateTimeInterface|string|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + */ + public function __construct($time = null, $tz = null); + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo(); + + /** + * Get a part of the EDD\Vendor\Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function __get($name); + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name); + + /** + * Set a part of the EDD\Vendor\Carbon object + * + * @param string $name + * @param string|int|DateTimeZone $value + * + * @throws UnknownSetterException|ReflectionException + * + * @return void + */ + public function __set($name, $value); + + /** + * The __set_state handler. + * + * @param string|array $dump + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump); + + /** + * Returns the list of properties to dump on serialize() called on. + * + * Only used by PHP < 7.4. + * + * @return array + */ + public function __sleep(); + + /** + * Format the instance as a string using the set format + * + * @example + * ``` + * echo Carbon::now(); // EDD\Vendor\Carbon instances can be cast to string + * ``` + * + * @return string + */ + public function __toString(); + + /** + * Add given units or interval to the current instance. + * + * @example $date->add('hour', 3) + * @example $date->add(15, 'days') + * @example $date->add(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function add($unit, $value = 1, $overflow = null); + + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function addRealUnit($unit, $value = 1); + + /** + * Add given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function addUnit($unit, $value = 1, $overflow = null); + + /** + * Add any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function addUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function ago($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance + * (second-precision). + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|null $date + * + * @return static + */ + public function average($date = null); + + /** + * Clone the current instance if it's mutable. + * + * This method is convenient to ensure you don't mutate the initial object + * but avoid to make a useless copy of it if it's already immutable. + * + * @return static + */ + public function avoidMutation(); + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->between('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = true): bool; + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenExcluded($date1, $date2): bool; + + /** + * Determines if the instance is between two others, bounds included. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenIncluded($date1, $date2): bool; + + /** + * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, + * or a calendar date (e.g. "10/29/2017") otherwise. + * + * Language, date and time formats will change according to the current locale. + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|null $referenceTime + * @param array $formats + * + * @return string + */ + public function calendar($referenceTime = null, array $formats = []); + + /** + * Checks if the (date)time string is in a given format and valid to create a + * new instance. + * + * @example + * ``` + * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true + * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function canBeCreatedFromFormat($date, $format); + + /** + * Return the EDD\Vendor\Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param EDD\Vendor\Carbon|\EDD\Vendor\Carbon\CarbonPeriod|\EDD\Vendor\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * + * @return static + */ + public function carbonize($date = null); + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeInterface + */ + public function cast(string $className); + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function ceil($precision = 1); + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function ceilUnit($unit, $precision = 1); + + /** + * Ceil the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function ceilWeek($weekStartsAt = null); + + /** + * Similar to native modify() method of DateTime but can handle more grammars. + * + * @example + * ``` + * echo Carbon::now()->change('next 2pm'); + * ``` + * + * @link https://php.net/manual/en/datetime.modify.php + * + * @param string $modifier + * + * @return static|false + */ + public function change($modifier); + + /** + * Cleanup properties attached to the public scope of DateTime when a dump of the date is requested. + * foreach ($date as $_) {} + * serializer($date) + * var_export($date) + * get_object_vars($date) + */ + public function cleanupDumpProperties(); + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone(); + + /** + * Get the closest date from the instance (second-precision). + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2); + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy(); + + /** + * Create a new EDD\Vendor\Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param DateTimeInterface|int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + #[ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null); + + /** + * Create a EDD\Vendor\Carbon instance from a specific format and a string in a given language. + * + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleFormat($format, $locale, $time, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from a specific ISO format and a string in a given language. + * + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from a timestamp and set the timezone (use default one if not specified). + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null); + + /** + * Create a EDD\Vendor\Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampMsUTC($timestamp); + + /** + * Create a EDD\Vendor\Carbon instance from an timestamp keeping the timezone to UTC. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp); + + /** + * Create a EDD\Vendor\Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null); + + /** + * Create a new safe EDD\Vendor\Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidDateException + * + * @return static|false + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null); + + /** + * Create a new EDD\Vendor\Carbon instance from a specific date and time using strict validation. + * + * @see create() + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null); + + /** + * Get/set the day of year. + * + * @param int|null $value new value for day of year if using as setter. + * + * @return static|int + */ + public function dayOfYear($value = null); + + /** + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = []); + + /** + * Get the difference by the given interval using a filter closure. + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @example + * ``` + * echo Carbon::tomorrow()->diffForHumans() . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 2]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday()) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; + * ``` + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get the difference in days rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true); + + /** + * Get the difference in days using a filter closure rounded down. + * + * @param Closure $callback + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in hours rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true); + + /** + * Get the difference in hours using a filter closure rounded down. + * + * @param Closure $callback + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in microseconds. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = true); + + /** + * Get the difference in milliseconds rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = true); + + /** + * Get the difference in minutes rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true); + + /** + * Get the difference in months rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true); + + /** + * Get the difference in quarters rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInQuarters($date = null, $absolute = true); + + /** + * Get the difference in hours rounded down using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true); + + /** + * Get the difference in microseconds using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true); + + /** + * Get the difference in milliseconds rounded down using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = true); + + /** + * Get the difference in minutes rounded down using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true); + + /** + * Get the difference in seconds using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true); + + /** + * Get the difference in seconds rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true); + + /** + * Get the difference in weekdays rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true); + + /** + * Get the difference in weekend days using a filter rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true); + + /** + * Get the difference in weeks rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true); + + /** + * Get the difference in years + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption); + + /** + * Modify to end of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function endOf($unit, ...$params); + + /** + * Resets the date to end of the century and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfCentury(); + * ``` + * + * @return static + */ + public function endOfCentury(); + + /** + * Resets the time to 23:59:59.999999 end of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDay(); + * ``` + * + * @return static + */ + public function endOfDay(); + + /** + * Resets the date to end of the decade and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDecade(); + * ``` + * + * @return static + */ + public function endOfDecade(); + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); + * ``` + * + * @return static + */ + public function endOfHour(); + + /** + * Resets the date to end of the millennium and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMillennium(); + * ``` + * + * @return static + */ + public function endOfMillennium(); + + /** + * Modify to end of current minute, seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); + * ``` + * + * @return static + */ + public function endOfMinute(); + + /** + * Resets the date to end of the month and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMonth(); + * ``` + * + * @return static + */ + public function endOfMonth(); + + /** + * Resets the date to end of the quarter and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfQuarter(); + * ``` + * + * @return static + */ + public function endOfQuarter(); + + /** + * Modify to end of current second, microseconds become 999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function endOfSecond(); + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; + * ``` + * + * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * + * @return static + */ + public function endOfWeek($weekEndsAt = null); + + /** + * Resets the date to end of the year and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfYear(); + * ``` + * + * @return static + */ + public function endOfYear(); + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->eq(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see equalTo() + * + * @return bool + */ + public function eq($date): bool; + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function equalTo($date): bool; + + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * @param callable $func + * + * @return mixed + */ + public static function executeWithLocale($locale, $func); + + /** + * Get the farthest date from the instance (second-precision). + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2); + + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null); + + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null); + + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null); + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInDays($date = null, $absolute = true); + + /** + * Get the difference in hours as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInHours($date = null, $absolute = true); + + /** + * Get the difference in minutes as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMinutes($date = null, $absolute = true); + + /** + * Get the difference in months as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMonths($date = null, $absolute = true); + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealDays($date = null, $absolute = true); + + /** + * Get the difference in hours as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealHours($date = null, $absolute = true); + + /** + * Get the difference in minutes as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMinutes($date = null, $absolute = true); + + /** + * Get the difference in months as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMonths($date = null, $absolute = true); + + /** + * Get the difference in seconds as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealSeconds($date = null, $absolute = true); + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealWeeks($date = null, $absolute = true); + + /** + * Get the difference in year as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealYears($date = null, $absolute = true); + + /** + * Get the difference in seconds as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInSeconds($date = null, $absolute = true); + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInWeeks($date = null, $absolute = true); + + /** + * Get the difference in year as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInYears($date = null, $absolute = true); + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function floor($precision = 1); + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function floorUnit($unit, $precision = 1); + + /** + * Truncate the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function floorWeek($weekStartsAt = null); + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() https://php.net/setlocale. + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat() instead. + * Deprecated since 2.55.0 + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format); + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function from($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function fromNow($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws InvalidFormatException + * + * @return static + */ + public static function fromSerialized($value); + + /** + * Register a custom macro. + * + * @param object|callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public static function genericMacro($macro, $priority = 0); + + /** + * Get a part of the EDD\Vendor\Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function get($name); + + /** + * Returns the alternative number for a given date property if available in the current locale. + * + * @param string $key date property + * + * @return string + */ + public function getAltNumber(string $key): string; + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales(); + + /** + * Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * + * @return Language[] + */ + public static function getAvailableLocalesInfo(); + + /** + * Returns list of calendar formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getCalendarFormats($locale = null); + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays(); + + /** + * Return the number of days since the start of the week (using the current locale or the first parameter + * if explicitly given). + * + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return int + */ + public function getDaysFromStartOfWeek(?int $weekStartsAt = null): int; + + /** + * Get the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @return string|null + */ + public static function getFallbackLocale(); + + /** + * List of replacements from date() format to isoFormat(). + * + * @return array + */ + public static function getFormatsToIsoReplacements(); + + /** + * Return default humanDiff() options (merged flags as integer). + * + * @return int + */ + public static function getHumanDiffOptions(); + + /** + * Returns list of locale formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getIsoFormats($locale = null); + + /** + * Returns list of locale units for ISO formatting. + * + * @return array + */ + public static function getIsoUnits(); + + /** + * {@inheritdoc} + * + * @return array + */ + #[ReturnTypeWillChange] + public static function getLastErrors(); + + /** + * Get the raw callable macro registered globally or locally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public function getLocalMacro($name); + + /** + * Get the translator of the current instance or the default if none set. + * + * @return \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface + */ + public function getLocalTranslator(); + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale(); + + /** + * Get the raw callable macro registered globally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public static function getMacro($name); + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt(); + + /** + * Returns the offset hour and minute formatted with +/- and a given separator (":" by default). + * For example, if the time zone is 9 hours 30 minutes, you'll get "+09:30", with "@@" as first + * argument, "+09@@30", with "" as first argument, "+0930". Negative offset will return something + * like "-12:00". + * + * @param string $separator string to place between hours and minutes (":" by default) + * + * @return string + */ + public function getOffsetString($separator = ':'); + + /** + * Returns a unit of the instance padded with 0 by default or any other string if specified. + * + * @param string $unit EDD\Vendor\Carbon unit name + * @param int $length Length of the output (2 by default) + * @param string $padString String to use for padding ("0" by default) + * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) + * + * @return string + */ + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = 0); + + /** + * Returns a timestamp rounded with the given precision (6 by default). + * + * @example getPreciseTimestamp() 1532087464437474 (microsecond maximum precision) + * @example getPreciseTimestamp(6) 1532087464437474 + * @example getPreciseTimestamp(5) 153208746443747 (1/100000 second precision) + * @example getPreciseTimestamp(4) 15320874644375 (1/10000 second precision) + * @example getPreciseTimestamp(3) 1532087464437 (millisecond precision) + * @example getPreciseTimestamp(2) 153208746444 (1/100 second precision) + * @example getPreciseTimestamp(1) 15320874644 (1/10 second precision) + * @example getPreciseTimestamp(0) 1532087464 (second precision) + * @example getPreciseTimestamp(-1) 153208746 (10 second precision) + * @example getPreciseTimestamp(-2) 15320875 (100 second precision) + * + * @param int $precision + * + * @return float + */ + public function getPreciseTimestamp($precision = 6); + + /** + * Returns current local settings. + * + * @return array + */ + public function getSettings(); + + /** + * Get the EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|static the current instance used for testing + */ + public static function getTestNow(); + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision); + + /** + * Returns the timestamp with millisecond precision. + * + * @return int + */ + public function getTimestampMs(); + + /** + * Get the translation of the current week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "", "_short" or "_min" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null); + + /** + * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedMinDayName($context = null); + + /** + * Get the translation of the current month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "" or "_short" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null); + + /** + * Get the translation of the current short week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortDayName($context = null); + + /** + * Get the translation of the current short month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortMonthName($context = null); + + /** + * Returns raw translation message for a given key. + * + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * + * @return string + */ + public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null); + + /** + * Returns raw translation message for a given key. + * + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * + * @return string + */ + public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null); + + /** + * Get the default translator instance in use. + * + * @return \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator(); + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt(); + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt(); + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays(); + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThan($date): bool; + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThanOrEqualTo($date): bool; + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function gt($date): bool; + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($date): bool; + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format); + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormatWithModifiers($date, $format): bool; + + /** + * Checks if macro is registered globally or locally. + * + * @param string $name + * + * @return bool + */ + public function hasLocalMacro($name); + + /** + * Return true if the current instance has its own translator. + * + * @return bool + */ + public function hasLocalTranslator(); + + /** + * Checks if macro is registered globally. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name); + + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time); + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow(); + + /** + * Create a EDD\Vendor\Carbon instance from a DateTime one. + * + * @param DateTimeInterface $date + * + * @return static + */ + public static function instance($date); + + /** + * Returns true if the current date matches the given string. + * + * @example + * ``` + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2018')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('Sunday')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('June')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:45')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:00')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12h')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3pm')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3am')); // false + * ``` + * + * @param string $tester day name, month name, hour, date, etc. as string + * + * @return bool + */ + public function is(string $tester); + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function isAfter($date): bool; + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function isBefore($date): bool; + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($date1, $date2, $equal = true): bool; + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @example + * ``` + * Carbon::now()->subYears(5)->isBirthday(); // true + * Carbon::now()->subYears(5)->subDay()->isBirthday(); // false + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-05')); // true + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null); + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::now()->isCurrentUnit('hour'); // true + * Carbon::now()->subHours(2)->isCurrentUnit('hour'); // false + * ``` + * + * @param string $unit The unit to test. + * + * @throws BadMethodCallException + * + * @return bool + */ + public function isCurrentUnit($unit); + + /** + * Checks if this day is a specific day of the week. + * + * @example + * ``` + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::WEDNESDAY); // true + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::FRIDAY); // false + * Carbon::parse('2019-07-17')->isDayOfWeek('Wednesday'); // true + * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false + * ``` + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek); + + /** + * Check if the instance is end of day. + * + * @example + * ``` + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:58.999999')->isEndOfDay(); // false + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(true); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(true); // false + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false); + + /** + * Returns true if the date was created using CarbonImmutable::endOfTime() + * + * @return bool + */ + public function isEndOfTime(): bool; + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @example + * ``` + * Carbon::now()->addHours(5)->isFuture(); // true + * Carbon::now()->subHours(5)->isFuture(); // false + * ``` + * + * @return bool + */ + public function isFuture(); + + /** + * Returns true if the current class/instance is immutable. + * + * @return bool + */ + public static function isImmutable(); + + /** + * Check if today is the last day of the Month + * + * @example + * ``` + * Carbon::parse('2019-02-28')->isLastOfMonth(); // true + * Carbon::parse('2019-03-28')->isLastOfMonth(); // false + * Carbon::parse('2019-03-30')->isLastOfMonth(); // false + * Carbon::parse('2019-03-31')->isLastOfMonth(); // true + * Carbon::parse('2019-04-30')->isLastOfMonth(); // true + * ``` + * + * @return bool + */ + public function isLastOfMonth(); + + /** + * Determines if the instance is a leap year. + * + * @example + * ``` + * Carbon::parse('2020-01-01')->isLeapYear(); // true + * Carbon::parse('2019-01-01')->isLeapYear(); // false + * ``` + * + * @return bool + */ + public function isLeapYear(); + + /** + * Determines if the instance is a long year (using ISO 8601 year). + * + * @example + * ``` + * Carbon::parse('2015-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-03')->isLongIsoYear(); // false + * Carbon::parse('2019-12-29')->isLongIsoYear(); // false + * Carbon::parse('2019-12-30')->isLongIsoYear(); // true + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongIsoYear(); + + /** + * Determines if the instance is a long year (using calendar year). + * + * ⚠️ This method completely ignores month and day to use the numeric year number, + * it's not correct if the exact date matters. For instance as `2019-12-30` is already + * in the first week of the 2020 year, if you want to know from this date if ISO week + * year 2020 is a long year, use `isLongIsoYear` instead. + * + * @example + * ``` + * Carbon::create(2015)->isLongYear(); // true + * Carbon::create(2016)->isLongYear(); // false + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear(); + + /** + * Check if the instance is midday. + * + * @example + * ``` + * Carbon::parse('2019-02-28 11:59:59.999999')->isMidday(); // false + * Carbon::parse('2019-02-28 12:00:00')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false + * ``` + * + * @return bool + */ + public function isMidday(); + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false + * ``` + * + * @return bool + */ + public function isMidnight(); + + /** + * Returns true if a property can be changed via setter. + * + * @param string $unit + * + * @return bool + */ + public static function isModifiableUnit($unit); + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable(); + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @example + * ``` + * Carbon::now()->subHours(5)->isPast(); // true + * Carbon::now()->addHours(5)->isPast(); // false + * ``` + * + * @return bool + */ + public function isPast(); + + /** + * Compares the formatted values of the two dates. + * + * @example + * ``` + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-12-13')); // true + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false + * ``` + * + * @param string $format date formats to compare. + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameAs($format, $date = null); + + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-01-01')); // true + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-02-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = true); + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-03-01')); // true + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-04-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|string|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = true); + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::parse('2019-01-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // true + * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false + * ``` + * + * @param string $unit singular unit string + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * + * @throws BadComparisonUnitException + * + * @return bool + */ + public function isSameUnit($unit, $date = null); + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:01')->isStartOfDay(); // false + * Carbon::parse('2019-02-28 00:00:00.000000')->isStartOfDay(true); // true + * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false); + + /** + * Returns true if the date was created using CarbonImmutable::startOfTime() + * + * @return bool + */ + public function isStartOfTime(): bool; + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled(); + + /** + * Determines if the instance is today. + * + * @example + * ``` + * Carbon::today()->isToday(); // true + * Carbon::tomorrow()->isToday(); // false + * ``` + * + * @return bool + */ + public function isToday(); + + /** + * Determines if the instance is tomorrow. + * + * @example + * ``` + * Carbon::tomorrow()->isTomorrow(); // true + * Carbon::yesterday()->isTomorrow(); // false + * ``` + * + * @return bool + */ + public function isTomorrow(); + + /** + * Determines if the instance is a weekday. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekday(); // false + * Carbon::parse('2019-07-15')->isWeekday(); // true + * ``` + * + * @return bool + */ + public function isWeekday(); + + /** + * Determines if the instance is a weekend day. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekend(); // true + * Carbon::parse('2019-07-15')->isWeekend(); // false + * ``` + * + * @return bool + */ + public function isWeekend(); + + /** + * Determines if the instance is yesterday. + * + * @example + * ``` + * Carbon::yesterday()->isYesterday(); // true + * Carbon::tomorrow()->isYesterday(); // false + * ``` + * + * @return bool + */ + public function isYesterday(); + + /** + * Format in the current language using ISO replacement patterns. + * + * @param string $format + * @param string|null $originalFormat provide context if a chunk has been passed alone + * + * @return string + */ + public function isoFormat(string $format, ?string $originalFormat = null): string; + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function isoWeek($week = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function isoWeekYear($year = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function isoWeekday($value = null); + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null); + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); + + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null); + + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null); + + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null); + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThan($date): bool; + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThanOrEqualTo($date): bool; + + /** + * Get/set the locale for the current instance. + * + * @param string|null $locale + * @param string ...$fallbackLocales + * + * @return $this|string + */ + public function locale(?string $locale = null, ...$fallbackLocales); + + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale); + + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale); + + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale); + + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale); + + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale); + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function lt($date): bool; + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($date): bool; + + /** + * Register a custom macro. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * Carbon::macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo Carbon::yesterday()->hours(11)->userFormat(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro); + + /** + * Make a EDD\Vendor\Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @throws InvalidFormatException + * + * @return static|null + */ + public static function make($var); + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function max($date = null); + + /** + * Create a EDD\Vendor\Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue(); + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null); + + /** + * Return the meridiem of the current time in the current locale. + * + * @param bool $isLower if true, returns lowercase variant if available in the current locale. + * + * @return string + */ + public function meridiem(bool $isLower = false): string; + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay(); + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function min($date = null); + + /** + * Create a EDD\Vendor\Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue(); + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null); + + /** + * Mix another object into the class. + * + * @example + * ``` + * Carbon::mixin(new class { + * public function addMoon() { + * return function () { + * return $this->addDays(30); + * }; + * } + * public function subMoon() { + * return function () { + * return $this->subDays(30); + * }; + * } + * }); + * $fullMoon = Carbon::create('2018-12-22'); + * $nextFullMoon = $fullMoon->addMoon(); + * $blackMoon = Carbon::create('2019-01-06'); + * $previousBlackMoon = $blackMoon->subMoon(); + * echo "$nextFullMoon\n"; + * echo "$previousBlackMoon\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin); + + /** + * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. + * + * @see https://php.net/manual/en/datetime.modify.php + * + * @return static|false + */ + #[ReturnTypeWillChange] + public function modify($modify); + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->ne(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($date): bool; + + /** + * Modify to the next occurrence of a given modifier such as a day of + * the week. If no modifier is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static|false + */ + public function next($modifier = null); + + /** + * Go forward to the next weekday. + * + * @return static + */ + public function nextWeekday(); + + /** + * Go forward to the next weekend day. + * + * @return static + */ + public function nextWeekendDay(); + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function notEqualTo($date): bool; + + /** + * Get a EDD\Vendor\Carbon instance for the current date and time. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null); + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz(); + + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek); + + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek); + + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek); + + /** + * Return a property with its ordinal. + * + * @param string $key + * @param string|null $period + * + * @return string + */ + public function ordinal(string $key, ?string $period = null): string; + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parse($time = null, $tz = null); + + /** + * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parseFromLocale($time, $locale = null, $tz = null); + + /** + * Returns standardized plural of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function pluralUnit(string $unit): string; + + /** + * Modify to the previous occurrence of a given modifier such as a day of + * the week. If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static|false + */ + public function previous($modifier = null); + + /** + * Go backward to the previous weekday. + * + * @return static + */ + public function previousWeekday(); + + /** + * Go backward to the previous weekend day. + * + * @return static + */ + public function previousWeekendDay(); + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|EDD\Vendor\Carbon|CarbonImmutable|null $end period end date + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function range($end = null, $interval = null, $unit = null); + + /** + * Call native PHP DateTime/DateTimeImmutable add() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawAdd(DateInterval $interval); + + /** + * Create a EDD\Vendor\Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function rawCreateFromFormat($format, $time, $tz = null); + + /** + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + public function rawFormat($format); + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function rawParse($time = null, $tz = null); + + /** + * Call native PHP DateTime/DateTimeImmutable sub() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawSub(DateInterval $interval); + + /** + * Remove all macros and generic macros. + */ + public static function resetMacros(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow(); + + /** + * Reset the format used to the default when type juggling a EDD\Vendor\Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow(); + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return CarbonInterface + */ + public function round($precision = 1, $function = 'round'); + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int $precision + * @param string $function + * + * @return CarbonInterface + */ + public function roundUnit($unit, $precision = 1, $function = 'round'); + + /** + * Round the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function roundWeek($weekStartsAt = null); + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight(); + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay(); + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform EDD\Vendor\Carbon object before the serialization. + * + * JSON serialize all EDD\Vendor\Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback); + + /** + * Set a part of the EDD\Vendor\Carbon object + * + * @param string|array $name + * @param string|int|DateTimeZone $value + * + * @throws ImmutableException|UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null); + + /** + * Set the date with gregorian year, month and day numbers. + * + * @see https://php.net/manual/en/datetime.setdate.php + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setDate($year, $month, $day); + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param EDD\Vendor\Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setDateFrom($date = null); + + /** + * Set the date and time all together. + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0); + + /** + * Set the date and time for this instance to that of the passed instance. + * + * @param EDD\Vendor\Carbon|DateTimeInterface $date + * + * @return static + */ + public function setDateTimeFrom($date = null); + + /** + * Set the day (keeping the current time) to the start of the week + the number of days passed as the first + * parameter. First day of week is driven by the locale unless explicitly set with the second parameter. + * + * @param int $numberOfDays number of days to add after the start of the current week + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return static + */ + public function setDaysFromStartOfWeek(int $numberOfDays, ?int $weekStartsAt = null); + + /** + * Set the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @param string $locale + */ + public static function setFallbackLocale($locale); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions); + + /** + * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. + * + * @see https://php.net/manual/en/datetime.setisodate.php + * + * @param int $year + * @param int $week + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setISODate($year, $week, $day = 1); + + /** + * Set the translator for the current instance. + * + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator + * + * @return $this + */ + public function setLocalTranslator(TranslatorInterface $translator); + + /** + * Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour); + + /** + * Set a EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock EDD\Vendor\Carbon instance + */ + public static function setTestNow($testNow = null); + + /** + * Set a EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock EDD\Vendor\Carbon instance + */ + public static function setTestNowAndTimezone($testNow = null, $tz = null); + + /** + * Resets the current time of the DateTime object to a different time. + * + * @see https://php.net/manual/en/datetime.settime.php + * + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTime($hour, $minute, $second = 0, $microseconds = 0); + + /** + * Set the hour, minute, second and microseconds for this instance to that of the passed instance. + * + * @param EDD\Vendor\Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setTimeFrom($date = null); + + /** + * Set the time by time string. + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time); + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimestamp($unixTimestamp); + + /** + * Set the instance's timezone from a string or object. + * + * @param DateTimeZone|string $value + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimezone($value); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let EDD\Vendor\Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * + * Set the default format used when type juggling a EDD\Vendor\Carbon instance to a string. + * + * @param string|Closure|null $format + * + * @return void + */ + public static function setToStringFormat($format); + + /** + * Set the default translator instance to use. + * + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator); + + /** + * Set specified unit to new given value. + * + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param int $value new value for given unit + * + * @return static + */ + public function setUnit($unit, $value = null); + + /** + * Set any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function setUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * + * Set if UTF8 will be used for localized date/time. + * + * @param bool $utf8 + */ + public static function setUtf8($utf8); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * + * Set the last day of week + * + * @param int|string $day week end day (or 'auto' to get the day before the first day of week + * from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekEndsAt($day); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * + * Set the first day of week + * + * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekStartsAt($day); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days); + + /** + * Set specific options. + * - strictMode: true|false|null + * - monthOverflow: true|false|null + * - yearOverflow: true|false|null + * - humanDiffOptions: int|null + * - toStringFormat: string|Closure|null + * - toJsonFormat: string|Closure|null + * - locale: string|null + * - timezone: \DateTimeZone|string|int|null + * - macros: array|null + * - genericMacros: array|null + * + * @param array $settings + * + * @return $this|static + */ + public function settings(array $settings); + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function shiftTimezone($value); + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths(); + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears(); + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + */ + public function since($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Returns standardized singular of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function singularUnit(string $unit): string; + + /** + * Modify to start of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function startOf($unit, ...$params); + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfCentury(); + * ``` + * + * @return static + */ + public function startOfCentury(); + + /** + * Resets the time to 00:00:00 start of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDay(); + * ``` + * + * @return static + */ + public function startOfDay(); + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDecade(); + * ``` + * + * @return static + */ + public function startOfDecade(); + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); + * ``` + * + * @return static + */ + public function startOfHour(); + + /** + * Resets the date to the first day of the millennium and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMillennium(); + * ``` + * + * @return static + */ + public function startOfMillennium(); + + /** + * Modify to start of current minute, seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); + * ``` + * + * @return static + */ + public function startOfMinute(); + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMonth(); + * ``` + * + * @return static + */ + public function startOfMonth(); + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfQuarter(); + * ``` + * + * @return static + */ + public function startOfQuarter(); + + /** + * Modify to start of current second, microseconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function startOfSecond(); + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; + * ``` + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return static + */ + public function startOfWeek($weekStartsAt = null); + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfYear(); + * ``` + * + * @return static + */ + public function startOfYear(); + + /** + * Subtract given units or interval to the current instance. + * + * @example $date->sub('hour', 3) + * @example $date->sub(15, 'days') + * @example $date->sub(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, $overflow = null); + + public function subRealUnit($unit, $value = 1); + + /** + * Subtract given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subUnit($unit, $value = 1, $overflow = null); + + /** + * Subtract any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function subUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * Subtract given units or interval to the current instance. + * + * @see sub() + * + * @param string|DateInterval $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subtract($unit, $value = 1, $overflow = null); + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @return string + */ + public function timespan($other = null, $timezone = null); + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + public function timestamp($unixTimestamp); + + /** + * @alias setTimezone + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function timezone($value); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * When comparing a value in the past to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the future to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the past to another value: + * 1 hour after + * 5 months after + * + * When comparing a value in the future to another value: + * 1 hour before + * 5 months before + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function to($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get default array representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toArray()); + * ``` + * + * @return array + */ + public function toArray(); + + /** + * Format the instance as ATOM + * + * @example + * ``` + * echo Carbon::now()->toAtomString(); + * ``` + * + * @return string + */ + public function toAtomString(); + + /** + * Format the instance as COOKIE + * + * @example + * ``` + * echo Carbon::now()->toCookieString(); + * ``` + * + * @return string + */ + public function toCookieString(); + + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate(); + + /** + * Format the instance as date + * + * @example + * ``` + * echo Carbon::now()->toDateString(); + * ``` + * + * @return string + */ + public function toDateString(); + + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime(); + + /** + * Return native toDateTimeImmutable PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTimeImmutable()); + * ``` + * + * @return DateTimeImmutable + */ + public function toDateTimeImmutable(); + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * echo "\n"; + * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second'); + + /** + * Format the instance as date and time + * + * @example + * ``` + * echo Carbon::now()->toDateTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second'); + + /** + * Format the instance with day, date and time + * + * @example + * ``` + * echo Carbon::now()->toDayDateTimeString(); + * ``` + * + * @return string + */ + public function toDayDateTimeString(); + + /** + * Format the instance as a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDateString(); + * ``` + * + * @return string + */ + public function toFormattedDateString(); + + /** + * Format the instance with the day, and a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDayDateString(); + * ``` + * + * @return string + */ + public function toFormattedDayDateString(): string; + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept: + * 1977-04-22T01:00:00-05:00). + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toISOString() . "\n"; + * echo Carbon::now('America/Toronto')->toISOString(true) . "\n"; + * ``` + * + * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. + * + * @return null|string + */ + public function toISOString($keepOffset = false); + + /** + * Return a immutable copy of the instance. + * + * @return CarbonImmutable + */ + public function toImmutable(); + + /** + * Format the instance as ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601String(); + * ``` + * + * @return string + */ + public function toIso8601String(); + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601ZuluString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toIso8601ZuluString($unitPrecision = 'second'); + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toJSON(); + * ``` + * + * @return null|string + */ + public function toJSON(); + + /** + * Return a mutable copy of the instance. + * + * @return EDD\Vendor\Carbon + */ + public function toMutable(); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function toNow($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject(); + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|EDD\Vendor\Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function toPeriod($end = null, $interval = null, $unit = null); + + /** + * Format the instance as RFC1036 + * + * @example + * ``` + * echo Carbon::now()->toRfc1036String(); + * ``` + * + * @return string + */ + public function toRfc1036String(); + + /** + * Format the instance as RFC1123 + * + * @example + * ``` + * echo Carbon::now()->toRfc1123String(); + * ``` + * + * @return string + */ + public function toRfc1123String(); + + /** + * Format the instance as RFC2822 + * + * @example + * ``` + * echo Carbon::now()->toRfc2822String(); + * ``` + * + * @return string + */ + public function toRfc2822String(); + + /** + * Format the instance as RFC3339 + * + * @param bool $extended + * + * @example + * ``` + * echo Carbon::now()->toRfc3339String() . "\n"; + * echo Carbon::now()->toRfc3339String(true) . "\n"; + * ``` + * + * @return string + */ + public function toRfc3339String($extended = false); + + /** + * Format the instance as RFC7231 + * + * @example + * ``` + * echo Carbon::now()->toRfc7231String(); + * ``` + * + * @return string + */ + public function toRfc7231String(); + + /** + * Format the instance as RFC822 + * + * @example + * ``` + * echo Carbon::now()->toRfc822String(); + * ``` + * + * @return string + */ + public function toRfc822String(); + + /** + * Format the instance as RFC850 + * + * @example + * ``` + * echo Carbon::now()->toRfc850String(); + * ``` + * + * @return string + */ + public function toRfc850String(); + + /** + * Format the instance as RSS + * + * @example + * ``` + * echo Carbon::now()->toRssString(); + * ``` + * + * @return string + */ + public function toRssString(); + + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString(); + + /** + * Format the instance as time + * + * @example + * ``` + * echo Carbon::now()->toTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second'); + + /** + * Format the instance as W3C + * + * @example + * ``` + * echo Carbon::now()->toW3cString(); + * ``` + * + * @return string + */ + public function toW3cString(); + + /** + * Create a EDD\Vendor\Carbon instance for today. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null); + + /** + * Create a EDD\Vendor\Carbon instance for tomorrow. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null); + + /** + * Translate using translation string or callback available. + * + * @param string $key + * @param array $parameters + * @param string|int|float|null $number + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface|null $translator + * @param bool $altNumbers + * + * @return string + */ + public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string; + + /** + * Returns the alternative number for a given integer if available in the current locale. + * + * @param int $number + * + * @return string + */ + public function translateNumber(int $number): string; + + /** + * Translate a time string from a locale to an other. + * + * @param string $timeString date/time/duration string to translate (may also contain English) + * @param string|null $from input locale of the $timeString parameter (`Carbon::getLocale()` by default) + * @param string|null $to output locale of the result returned (`"en"` by default) + * @param int $mode specify what to translate with options: + * - self::TRANSLATE_ALL (default) + * - CarbonInterface::TRANSLATE_MONTHS + * - CarbonInterface::TRANSLATE_DAYS + * - CarbonInterface::TRANSLATE_UNITS + * - CarbonInterface::TRANSLATE_MERIDIEM + * You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS + * + * @return string + */ + public static function translateTimeString($timeString, $from = null, $to = null, $mode = self::TRANSLATE_ALL); + + /** + * Translate a time string from the current locale (`$date->locale()`) to an other. + * + * @param string $timeString time string to translate + * @param string|null $to output locale of the result returned ("en" by default) + * + * @return string + */ + public function translateTimeStringTo($timeString, $to = null); + + /** + * Translate using translation string or callback available. + * + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator + * @param string $key + * @param array $parameters + * @param null $number + * + * @return string + */ + public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string; + + /** + * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) + * but translate words whenever possible (months, day names, etc.) using the current locale. + * + * @param string $format + * + * @return string + */ + public function translatedFormat(string $format): string; + + /** + * Set the timezone or returns the timezone name if no arguments passed. + * + * @param DateTimeZone|string $value + * + * @return static|string + */ + public function tz($value = null); + + /** + * @alias getTimestamp + * + * Returns the UNIX timestamp for the current date. + * + * @return int + */ + public function unix(); + + /** + * @alias to + * + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function until($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode($strictModeEnabled = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true); + + /** + * Set the instance's timezone to UTC. + * + * @return static + */ + public function utc(); + + /** + * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. + * + * @param int|null $minuteOffset + * + * @return int|static + */ + public function utcOffset(?int $minuteOffset = null); + + /** + * Returns the milliseconds timestamps used amongst other by Date javascript objects. + * + * @return float + */ + public function valueOf(); + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function week($week = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Get/set the weekday from 0 (Sunday) to 6 (Saturday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function weekday($value = null); + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function weeksInYear($dayOfWeek = null, $dayOfYear = null); + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @template T + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock EDD\Vendor\Carbon instance + * @param Closure(): T $callback + * + * @return T + */ + public static function withTestNow($testNow, $callback); + + /** + * Create a EDD\Vendor\Carbon instance for yesterday. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null); + + // +} diff --git a/libraries/Carbon/src/Carbon/CarbonInterval.php b/libraries/Carbon/src/Carbon/CarbonInterval.php new file mode 100644 index 00000000000..be94d89e0ca --- /dev/null +++ b/libraries/Carbon/src/Carbon/CarbonInterval.php @@ -0,0 +1,3054 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Carbon\Exceptions\BadFluentConstructorException; +use EDD\Vendor\Carbon\Exceptions\BadFluentSetterException; +use EDD\Vendor\Carbon\Exceptions\InvalidCastException; +use EDD\Vendor\Carbon\Exceptions\InvalidIntervalException; +use EDD\Vendor\Carbon\Exceptions\OutOfRangeException; +use EDD\Vendor\Carbon\Exceptions\ParseErrorException; +use EDD\Vendor\Carbon\Exceptions\UnitNotConfiguredException; +use EDD\Vendor\Carbon\Exceptions\UnknownGetterException; +use EDD\Vendor\Carbon\Exceptions\UnknownSetterException; +use EDD\Vendor\Carbon\Exceptions\UnknownUnitException; +use EDD\Vendor\Carbon\Traits\IntervalRounding; +use EDD\Vendor\Carbon\Traits\IntervalStep; +use EDD\Vendor\Carbon\Traits\MagicParameter; +use EDD\Vendor\Carbon\Traits\Mixin; +use EDD\Vendor\Carbon\Traits\Options; +use EDD\Vendor\Carbon\Traits\ToStringFormat; +use Closure; +use DateInterval; +use DateMalformedIntervalStringException; +use DateTimeInterface; +use DateTimeZone; +use Exception; +use InvalidArgumentException; +use ReflectionException; +use ReturnTypeWillChange; +use RuntimeException; +use Throwable; + +/** + * A simple API extension for DateInterval. + * The implementation provides helpers to handle weeks but only days are saved. + * Weeks are calculated based on the total days of the current instance. + * + * @property int $years Total years of the current interval. + * @property int $months Total months of the current interval. + * @property int $weeks Total weeks of the current interval calculated from the days. + * @property int $dayz Total days of the current interval (weeks * 7 + days). + * @property int $hours Total hours of the current interval. + * @property int $minutes Total minutes of the current interval. + * @property int $seconds Total seconds of the current interval. + * @property int $microseconds Total microseconds of the current interval. + * @property int $milliseconds Total milliseconds of the current interval. + * @property int $microExcludeMilli Remaining microseconds without the milliseconds. + * @property int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7). + * @property int $daysExcludeWeeks alias of dayzExcludeWeeks + * @property-read float $totalYears Number of years equivalent to the interval. + * @property-read float $totalMonths Number of months equivalent to the interval. + * @property-read float $totalWeeks Number of weeks equivalent to the interval. + * @property-read float $totalDays Number of days equivalent to the interval. + * @property-read float $totalDayz Alias for totalDays. + * @property-read float $totalHours Number of hours equivalent to the interval. + * @property-read float $totalMinutes Number of minutes equivalent to the interval. + * @property-read float $totalSeconds Number of seconds equivalent to the interval. + * @property-read float $totalMilliseconds Number of milliseconds equivalent to the interval. + * @property-read float $totalMicroseconds Number of microseconds equivalent to the interval. + * @property-read string $locale locale of the current instance + * + * @method static CarbonInterval years($years = 1) Create instance specifying a number of years or modify the number of years if called on an instance. + * @method static CarbonInterval year($years = 1) Alias for years() + * @method static CarbonInterval months($months = 1) Create instance specifying a number of months or modify the number of months if called on an instance. + * @method static CarbonInterval month($months = 1) Alias for months() + * @method static CarbonInterval weeks($weeks = 1) Create instance specifying a number of weeks or modify the number of weeks if called on an instance. + * @method static CarbonInterval week($weeks = 1) Alias for weeks() + * @method static CarbonInterval days($days = 1) Create instance specifying a number of days or modify the number of days if called on an instance. + * @method static CarbonInterval dayz($days = 1) Alias for days() + * @method static CarbonInterval daysExcludeWeeks($days = 1) Create instance specifying a number of days or modify the number of days (keeping the current number of weeks) if called on an instance. + * @method static CarbonInterval dayzExcludeWeeks($days = 1) Alias for daysExcludeWeeks() + * @method static CarbonInterval day($days = 1) Alias for days() + * @method static CarbonInterval hours($hours = 1) Create instance specifying a number of hours or modify the number of hours if called on an instance. + * @method static CarbonInterval hour($hours = 1) Alias for hours() + * @method static CarbonInterval minutes($minutes = 1) Create instance specifying a number of minutes or modify the number of minutes if called on an instance. + * @method static CarbonInterval minute($minutes = 1) Alias for minutes() + * @method static CarbonInterval seconds($seconds = 1) Create instance specifying a number of seconds or modify the number of seconds if called on an instance. + * @method static CarbonInterval second($seconds = 1) Alias for seconds() + * @method static CarbonInterval milliseconds($milliseconds = 1) Create instance specifying a number of milliseconds or modify the number of milliseconds if called on an instance. + * @method static CarbonInterval millisecond($milliseconds = 1) Alias for milliseconds() + * @method static CarbonInterval microseconds($microseconds = 1) Create instance specifying a number of microseconds or modify the number of microseconds if called on an instance. + * @method static CarbonInterval microsecond($microseconds = 1) Alias for microseconds() + * @method $this addYears(int $years) Add given number of years to the current interval + * @method $this subYears(int $years) Subtract given number of years to the current interval + * @method $this addMonths(int $months) Add given number of months to the current interval + * @method $this subMonths(int $months) Subtract given number of months to the current interval + * @method $this addWeeks(int|float $weeks) Add given number of weeks to the current interval + * @method $this subWeeks(int|float $weeks) Subtract given number of weeks to the current interval + * @method $this addDays(int|float $days) Add given number of days to the current interval + * @method $this subDays(int|float $days) Subtract given number of days to the current interval + * @method $this addHours(int|float $hours) Add given number of hours to the current interval + * @method $this subHours(int|float $hours) Subtract given number of hours to the current interval + * @method $this addMinutes(int|float $minutes) Add given number of minutes to the current interval + * @method $this subMinutes(int|float $minutes) Subtract given number of minutes to the current interval + * @method $this addSeconds(int|float $seconds) Add given number of seconds to the current interval + * @method $this subSeconds(int|float $seconds) Subtract given number of seconds to the current interval + * @method $this addMilliseconds(int|float $milliseconds) Add given number of milliseconds to the current interval + * @method $this subMilliseconds(int|float $milliseconds) Subtract given number of milliseconds to the current interval + * @method $this addMicroseconds(int|float $microseconds) Add given number of microseconds to the current interval + * @method $this subMicroseconds(int|float $microseconds) Subtract given number of microseconds to the current interval + * @method $this roundYear(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(int|float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(int|float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(int|float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(int|float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(int|float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(int|float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(int|float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(int|float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundWeek(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundWeeks(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorWeek(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorWeeks(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilWeek(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilWeeks(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundDay(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(int|float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(int|float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(int|float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(int|float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(int|float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(int|float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(int|float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(int|float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(int|float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(int|float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(int|float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(int|float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(int|float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(int|float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(int|float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(int|float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(int|float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(int|float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(int|float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(int|float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(int|float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(int|float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(int|float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(int|float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(int|float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(int|float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(int|float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(int|float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(int|float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(int|float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(int|float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(int|float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(int|float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(int|float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(int|float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(int|float $precision = 1) Ceil the current instance microsecond with given precision. + */ +class CarbonInterval extends DateInterval implements CarbonConverterInterface +{ + use IntervalRounding; + use IntervalStep; + use MagicParameter; + use Mixin { + Mixin::mixin as baseMixin; + } + use Options; + use ToStringFormat; + + /** + * Interval spec period designators + */ + public const PERIOD_PREFIX = 'P'; + public const PERIOD_YEARS = 'Y'; + public const PERIOD_MONTHS = 'M'; + public const PERIOD_DAYS = 'D'; + public const PERIOD_TIME_PREFIX = 'T'; + public const PERIOD_HOURS = 'H'; + public const PERIOD_MINUTES = 'M'; + public const PERIOD_SECONDS = 'S'; + + /** + * A translator to ... er ... translate stuff + * + * @var \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * @var array|null + */ + protected static $cascadeFactors; + + /** + * @var array + */ + protected static $formats = [ + 'y' => 'y', + 'Y' => 'y', + 'o' => 'y', + 'm' => 'm', + 'n' => 'm', + 'W' => 'weeks', + 'd' => 'd', + 'j' => 'd', + 'z' => 'd', + 'h' => 'h', + 'g' => 'h', + 'H' => 'h', + 'G' => 'h', + 'i' => 'i', + 's' => 's', + 'u' => 'micro', + 'v' => 'milli', + ]; + + /** + * @var array|null + */ + private static $flipCascadeFactors; + + /** + * @var bool + */ + private static $floatSettersEnabled = false; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = []; + + /** + * Timezone handler for settings() method. + * + * @var mixed + */ + protected $tzName; + + /** + * Set the instance's timezone from a string or object. + * + * @param \DateTimeZone|string $tzName + * + * @return static + */ + public function setTimezone($tzName) + { + $this->tzName = $tzName; + + return $this; + } + + /** + * @internal + * + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param \DateTimeZone|string $tzName + * + * @return static + */ + public function shiftTimezone($tzName) + { + $this->tzName = $tzName; + + return $this; + } + + /** + * Mapping of units and factors for cascading. + * + * Should only be modified by changing the factors or referenced constants. + * + * @return array + */ + public static function getCascadeFactors() + { + return static::$cascadeFactors ?: static::getDefaultCascadeFactors(); + } + + protected static function getDefaultCascadeFactors(): array + { + return [ + 'milliseconds' => [Carbon::MICROSECONDS_PER_MILLISECOND, 'microseconds'], + 'seconds' => [Carbon::MILLISECONDS_PER_SECOND, 'milliseconds'], + 'minutes' => [Carbon::SECONDS_PER_MINUTE, 'seconds'], + 'hours' => [Carbon::MINUTES_PER_HOUR, 'minutes'], + 'dayz' => [Carbon::HOURS_PER_DAY, 'hours'], + 'weeks' => [Carbon::DAYS_PER_WEEK, 'dayz'], + 'months' => [Carbon::WEEKS_PER_MONTH, 'weeks'], + 'years' => [Carbon::MONTHS_PER_YEAR, 'months'], + ]; + } + + private static function standardizeUnit($unit) + { + $unit = rtrim($unit, 'sz').'s'; + + return $unit === 'days' ? 'dayz' : $unit; + } + + private static function getFlipCascadeFactors() + { + if (!self::$flipCascadeFactors) { + self::$flipCascadeFactors = []; + + foreach (static::getCascadeFactors() as $to => [$factor, $from]) { + self::$flipCascadeFactors[self::standardizeUnit($from)] = [self::standardizeUnit($to), $factor]; + } + } + + return self::$flipCascadeFactors; + } + + /** + * Set default cascading factors for ->cascade() method. + * + * @param array $cascadeFactors + */ + public static function setCascadeFactors(array $cascadeFactors) + { + self::$flipCascadeFactors = null; + static::$cascadeFactors = $cascadeFactors; + } + + /** + * This option allow you to opt-in for the EDD\Vendor\Carbon 3 behavior where float + * values will no longer be cast to integer (so truncated). + * + * ⚠️ This settings will be applied globally, which mean your whole application + * code including the third-party dependencies that also may use EDD\Vendor\Carbon will + * adopt the new behavior. + */ + public static function enableFloatSetters(bool $floatSettersEnabled = true): void + { + self::$floatSettersEnabled = $floatSettersEnabled; + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Create a new CarbonInterval instance. + * + * @param Closure|DateInterval|string|int|null $years + * @param int|float|null $months + * @param int|float|null $weeks + * @param int|float|null $days + * @param int|float|null $hours + * @param int|float|null $minutes + * @param int|float|null $seconds + * @param int|float|null $microseconds + * + * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. + */ + public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + { + if ($years instanceof Closure) { + $this->step = $years; + $years = null; + } + + if ($years instanceof DateInterval) { + parent::__construct(static::getDateIntervalSpec($years)); + $this->f = $years->f; + self::copyNegativeUnits($years, $this); + + return; + } + + $spec = $years; + $isStringSpec = (\is_string($spec) && !preg_match('/^[\d.]/', $spec)); + + if (!$isStringSpec || (float) $years) { + $spec = static::PERIOD_PREFIX; + + $spec .= $years > 0 ? $years.static::PERIOD_YEARS : ''; + $spec .= $months > 0 ? $months.static::PERIOD_MONTHS : ''; + + $specDays = 0; + $specDays += $weeks > 0 ? $weeks * static::getDaysPerWeek() : 0; + $specDays += $days > 0 ? $days : 0; + + $spec .= $specDays > 0 ? $specDays.static::PERIOD_DAYS : ''; + + if ($hours > 0 || $minutes > 0 || $seconds > 0) { + $spec .= static::PERIOD_TIME_PREFIX; + $spec .= $hours > 0 ? $hours.static::PERIOD_HOURS : ''; + $spec .= $minutes > 0 ? $minutes.static::PERIOD_MINUTES : ''; + $spec .= $seconds > 0 ? $seconds.static::PERIOD_SECONDS : ''; + } + + if ($spec === static::PERIOD_PREFIX) { + // Allow the zero interval. + $spec .= '0'.static::PERIOD_YEARS; + } + } + + try { + parent::__construct($spec); + } catch (Throwable $exception) { + try { + parent::__construct('PT0S'); + + if ($isStringSpec) { + if (!preg_match('/^P + (?:(?[+-]?\d*(?:\.\d+)?)Y)? + (?:(?[+-]?\d*(?:\.\d+)?)M)? + (?:(?[+-]?\d*(?:\.\d+)?)W)? + (?:(?[+-]?\d*(?:\.\d+)?)D)? + (?:T + (?:(?[+-]?\d*(?:\.\d+)?)H)? + (?:(?[+-]?\d*(?:\.\d+)?)M)? + (?:(?[+-]?\d*(?:\.\d+)?)S)? + )? + $/x', $spec, $match)) { + throw new InvalidArgumentException("Invalid duration: $spec"); + } + + $years = (float) ($match['year'] ?? 0); + $this->assertSafeForInteger('year', $years); + $months = (float) ($match['month'] ?? 0); + $this->assertSafeForInteger('month', $months); + $weeks = (float) ($match['week'] ?? 0); + $this->assertSafeForInteger('week', $weeks); + $days = (float) ($match['day'] ?? 0); + $this->assertSafeForInteger('day', $days); + $hours = (float) ($match['hour'] ?? 0); + $this->assertSafeForInteger('hour', $hours); + $minutes = (float) ($match['minute'] ?? 0); + $this->assertSafeForInteger('minute', $minutes); + $seconds = (float) ($match['second'] ?? 0); + $this->assertSafeForInteger('second', $seconds); + } + + $totalDays = (($weeks * static::getDaysPerWeek()) + $days); + $this->assertSafeForInteger('days total (including weeks)', $totalDays); + + $this->y = (int) $years; + $this->m = (int) $months; + $this->d = (int) $totalDays; + $this->h = (int) $hours; + $this->i = (int) $minutes; + $this->s = (int) $seconds; + + if ( + ((float) $this->y) !== $years || + ((float) $this->m) !== $months || + ((float) $this->d) !== $totalDays || + ((float) $this->h) !== $hours || + ((float) $this->i) !== $minutes || + ((float) $this->s) !== $seconds + ) { + $this->add(static::fromString( + ($years - $this->y).' years '. + ($months - $this->m).' months '. + ($totalDays - $this->d).' days '. + ($hours - $this->h).' hours '. + ($minutes - $this->i).' minutes '. + ($seconds - $this->s).' seconds ' + )); + } + } catch (Throwable $secondException) { + throw $secondException instanceof OutOfRangeException ? $secondException : $exception; + } + } + + if ($microseconds !== null) { + $this->f = $microseconds / Carbon::MICROSECONDS_PER_SECOND; + } + } + + /** + * Returns the factor for a given source-to-target couple. + * + * @param string $source + * @param string $target + * + * @return int|float|null + */ + public static function getFactor($source, $target) + { + $source = self::standardizeUnit($source); + $target = self::standardizeUnit($target); + $factors = self::getFlipCascadeFactors(); + + if (isset($factors[$source])) { + [$to, $factor] = $factors[$source]; + + if ($to === $target) { + return $factor; + } + + return $factor * static::getFactor($to, $target); + } + + return null; + } + + /** + * Returns the factor for a given source-to-target couple if set, + * else try to find the appropriate constant as the factor, such as Carbon::DAYS_PER_WEEK. + * + * @param string $source + * @param string $target + * + * @return int|float|null + */ + public static function getFactorWithDefault($source, $target) + { + $factor = self::getFactor($source, $target); + + if ($factor) { + return $factor; + } + + static $defaults = [ + 'month' => ['year' => Carbon::MONTHS_PER_YEAR], + 'week' => ['month' => Carbon::WEEKS_PER_MONTH], + 'day' => ['week' => Carbon::DAYS_PER_WEEK], + 'hour' => ['day' => Carbon::HOURS_PER_DAY], + 'minute' => ['hour' => Carbon::MINUTES_PER_HOUR], + 'second' => ['minute' => Carbon::SECONDS_PER_MINUTE], + 'millisecond' => ['second' => Carbon::MILLISECONDS_PER_SECOND], + 'microsecond' => ['millisecond' => Carbon::MICROSECONDS_PER_MILLISECOND], + ]; + + return $defaults[$source][$target] ?? null; + } + + /** + * Returns current config for days per week. + * + * @return int|float + */ + public static function getDaysPerWeek() + { + return static::getFactor('dayz', 'weeks') ?: Carbon::DAYS_PER_WEEK; + } + + /** + * Returns current config for hours per day. + * + * @return int|float + */ + public static function getHoursPerDay() + { + return static::getFactor('hours', 'dayz') ?: Carbon::HOURS_PER_DAY; + } + + /** + * Returns current config for minutes per hour. + * + * @return int|float + */ + public static function getMinutesPerHour() + { + return static::getFactor('minutes', 'hours') ?: Carbon::MINUTES_PER_HOUR; + } + + /** + * Returns current config for seconds per minute. + * + * @return int|float + */ + public static function getSecondsPerMinute() + { + return static::getFactor('seconds', 'minutes') ?: Carbon::SECONDS_PER_MINUTE; + } + + /** + * Returns current config for microseconds per second. + * + * @return int|float + */ + public static function getMillisecondsPerSecond() + { + return static::getFactor('milliseconds', 'seconds') ?: Carbon::MILLISECONDS_PER_SECOND; + } + + /** + * Returns current config for microseconds per second. + * + * @return int|float + */ + public static function getMicrosecondsPerMillisecond() + { + return static::getFactor('microseconds', 'milliseconds') ?: Carbon::MICROSECONDS_PER_MILLISECOND; + } + + /** + * Create a new CarbonInterval instance from specific values. + * This is an alias for the constructor that allows better fluent + * syntax as it allows you to do CarbonInterval::create(1)->fn() rather than + * (new CarbonInterval(1))->fn(). + * + * @param int $years + * @param int $months + * @param int $weeks + * @param int $days + * @param int $hours + * @param int $minutes + * @param int $seconds + * @param int $microseconds + * + * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. + * + * @return static + */ + public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + { + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $microseconds); + } + + /** + * Parse a string into a new CarbonInterval object according to the specified format. + * + * @example + * ``` + * echo Carboninterval::createFromFormat('H:i', '1:30'); + * ``` + * + * @param string $format Format of the $interval input string + * @param string|null $interval Input string to convert into an interval + * + * @throws \EDD\Vendor\Carbon\Exceptions\ParseErrorException when the $interval cannot be parsed as an interval. + * + * @return static + */ + public static function createFromFormat(string $format, ?string $interval) + { + $instance = new static(0); + $length = mb_strlen($format); + + if (preg_match('/s([,.])([uv])$/', $format, $match)) { + $interval = explode($match[1], $interval); + $index = \count($interval) - 1; + $interval[$index] = str_pad($interval[$index], $match[2] === 'v' ? 3 : 6, '0'); + $interval = implode($match[1], $interval); + } + + $interval = $interval ?? ''; + + for ($index = 0; $index < $length; $index++) { + $expected = mb_substr($format, $index, 1); + $nextCharacter = mb_substr($interval, 0, 1); + $unit = static::$formats[$expected] ?? null; + + if ($unit) { + if (!preg_match('/^-?\d+/', $interval, $match)) { + throw new ParseErrorException('number', $nextCharacter); + } + + $interval = mb_substr($interval, mb_strlen($match[0])); + $instance->$unit += (int) ($match[0]); + + continue; + } + + if ($nextCharacter !== $expected) { + throw new ParseErrorException( + "'$expected'", + $nextCharacter, + 'Allowed substitutes for interval formats are '.implode(', ', array_keys(static::$formats))."\n". + 'See https://php.net/manual/en/function.date.php for their meaning' + ); + } + + $interval = mb_substr($interval, 1); + } + + if ($interval !== '') { + throw new ParseErrorException( + 'end of string', + $interval + ); + } + + return $instance; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + $date = new static(0); + $date->copyProperties($this); + $date->step = $this->step; + + return $date; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return $this->copy(); + } + + /** + * Provide static helpers to create instances. Allows CarbonInterval::years(3). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @return static|null + */ + public static function __callStatic($method, $parameters) + { + try { + $interval = new static(0); + $localStrictModeEnabled = $interval->localStrictModeEnabled; + $interval->localStrictModeEnabled = true; + + $result = static::hasMacro($method) + ? static::bindMacroContext(null, function () use (&$method, &$parameters, &$interval) { + return $interval->callMacro($method, $parameters); + }) + : $interval->$method(...$parameters); + + $interval->localStrictModeEnabled = $localStrictModeEnabled; + + return $result; + } catch (BadFluentSetterException $exception) { + if (Carbon::isStrictModeEnabled()) { + throw new BadFluentConstructorException($method, 0, $exception); + } + + return null; + } + } + + /** + * Evaluate the PHP generated by var_export() and recreate the exported CarbonInterval instance. + * + * @param array $dump data as exported by var_export() + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump) + { + /** @noinspection PhpVoidFunctionResultUsedInspection */ + /** @var DateInterval $dateInterval */ + $dateInterval = parent::__set_state($dump); + + return static::instance($dateInterval); + } + + /** + * Return the current context from inside a macro callee or a new one if static. + * + * @return static + */ + protected static function this() + { + return end(static::$macroContextStack) ?: new static(0); + } + + /** + * Creates a CarbonInterval from string. + * + * Format: + * + * Suffix | Unit | Example | DateInterval expression + * -------|---------|---------|------------------------ + * y | years | 1y | P1Y + * mo | months | 3mo | P3M + * w | weeks | 2w | P2W + * d | days | 28d | P28D + * h | hours | 4h | PT4H + * m | minutes | 12m | PT12M + * s | seconds | 59s | PT59S + * + * e. g. `1w 3d 4h 32m 23s` is converted to 10 days 4 hours 32 minutes and 23 seconds. + * + * Special cases: + * - An empty string will return a zero interval + * - Fractions are allowed for weeks, days, hours and minutes and will be converted + * and rounded to the next smaller value (caution: 0.5w = 4d) + * + * @param string $intervalDefinition + * + * @return static + */ + public static function fromString($intervalDefinition) + { + if (empty($intervalDefinition)) { + return new static(0); + } + + $years = 0; + $months = 0; + $weeks = 0; + $days = 0; + $hours = 0; + $minutes = 0; + $seconds = 0; + $milliseconds = 0; + $microseconds = 0; + + $pattern = '/(\d+(?:\.\d+)?)\h*([^\d\h]*)/i'; + preg_match_all($pattern, $intervalDefinition, $parts, PREG_SET_ORDER); + + while ([$part, $value, $unit] = array_shift($parts)) { + $intValue = (int) $value; + $fraction = (float) $value - $intValue; + + // Fix calculation precision + switch (round($fraction, 6)) { + case 1: + $fraction = 0; + $intValue++; + + break; + case 0: + $fraction = 0; + + break; + } + + switch ($unit === 'µs' ? 'µs' : strtolower($unit)) { + case 'millennia': + case 'millennium': + $years += $intValue * CarbonInterface::YEARS_PER_MILLENNIUM; + + break; + + case 'century': + case 'centuries': + $years += $intValue * CarbonInterface::YEARS_PER_CENTURY; + + break; + + case 'decade': + case 'decades': + $years += $intValue * CarbonInterface::YEARS_PER_DECADE; + + break; + + case 'year': + case 'years': + case 'y': + case 'yr': + case 'yrs': + $years += $intValue; + + break; + + case 'quarter': + case 'quarters': + $months += $intValue * CarbonInterface::MONTHS_PER_QUARTER; + + break; + + case 'month': + case 'months': + case 'mo': + case 'mos': + $months += $intValue; + + break; + + case 'week': + case 'weeks': + case 'w': + $weeks += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getDaysPerWeek(), 'd']; + } + + break; + + case 'day': + case 'days': + case 'd': + $days += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getHoursPerDay(), 'h']; + } + + break; + + case 'hour': + case 'hours': + case 'h': + $hours += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getMinutesPerHour(), 'm']; + } + + break; + + case 'minute': + case 'minutes': + case 'm': + $minutes += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getSecondsPerMinute(), 's']; + } + + break; + + case 'second': + case 'seconds': + case 's': + $seconds += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getMillisecondsPerSecond(), 'ms']; + } + + break; + + case 'millisecond': + case 'milliseconds': + case 'milli': + case 'ms': + $milliseconds += $intValue; + + if ($fraction) { + $microseconds += round($fraction * static::getMicrosecondsPerMillisecond()); + } + + break; + + case 'microsecond': + case 'microseconds': + case 'micro': + case 'µs': + $microseconds += $intValue; + + break; + + default: + throw new InvalidIntervalException( + sprintf('Invalid part %s in definition %s', $part, $intervalDefinition) + ); + } + } + + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $milliseconds * Carbon::MICROSECONDS_PER_MILLISECOND + $microseconds); + } + + /** + * Creates a CarbonInterval from string using a different locale. + * + * @param string $interval interval string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be used instead. + * + * @return static + */ + public static function parseFromLocale($interval, $locale = null) + { + return static::fromString(Carbon::translateTimeString($interval, $locale ?: static::getLocale(), 'en')); + } + + private static function castIntervalToClass(DateInterval $interval, string $className, array $skip = []) + { + $mainClass = DateInterval::class; + + if (!is_a($className, $mainClass, true)) { + throw new InvalidCastException("$className is not a sub-class of $mainClass."); + } + + $microseconds = $interval->f; + $instance = new $className(static::getDateIntervalSpec($interval, false, $skip)); + + if ($microseconds) { + $instance->f = $microseconds; + } + + if ($interval instanceof self && is_a($className, self::class, true)) { + self::copyStep($interval, $instance); + } + + self::copyNegativeUnits($interval, $instance); + + return $instance; + } + + private static function copyNegativeUnits(DateInterval $from, DateInterval $to): void + { + $to->invert = $from->invert; + + foreach (['y', 'm', 'd', 'h', 'i', 's'] as $unit) { + if ($from->$unit < 0) { + $to->$unit *= -1; + } + } + } + + private static function copyStep(self $from, self $to): void + { + $to->setStep($from->getStep()); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateInterval + */ + public function cast(string $className) + { + return self::castIntervalToClass($this, $className); + } + + /** + * Create a CarbonInterval instance from a DateInterval one. Can not instance + * DateInterval objects created from DateTime::diff() as you can't externally + * set the $days field. + * + * @param DateInterval $interval + * @param bool $skipCopy set to true to return the passed object + * (without copying it) if it's already of the + * current class + * + * @return static + */ + public static function instance(DateInterval $interval, array $skip = [], bool $skipCopy = false) + { + if ($skipCopy && $interval instanceof static) { + return $interval; + } + + return self::castIntervalToClass($interval, static::class, $skip); + } + + /** + * Make a CarbonInterval instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be intervals (skip dates + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed|int|DateInterval|string|Closure|null $interval interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * @param bool $skipCopy set to true to return the passed object + * (without copying it) if it's already of the + * current class + * + * @return static|null + */ + public static function make($interval, $unit = null, bool $skipCopy = false) + { + if ($unit) { + $interval = "$interval ".Carbon::pluralUnit($unit); + } + + if ($interval instanceof DateInterval) { + return static::instance($interval, [], $skipCopy); + } + + if ($interval instanceof Closure) { + return new static($interval); + } + + if (!\is_string($interval)) { + return null; + } + + return static::makeFromString($interval); + } + + protected static function makeFromString(string $interval) + { + $interval = preg_replace('/\s+/', ' ', trim($interval)); + + if (preg_match('/^P[T\d]/', $interval)) { + return new static($interval); + } + + if (preg_match('/^(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+$/i', $interval)) { + return static::fromString($interval); + } + + // @codeCoverageIgnoreStart + try { + /** @var static $interval */ + $interval = static::createFromDateString($interval); + } catch (DateMalformedIntervalStringException $e) { + return null; + } + // @codeCoverageIgnoreEnd + + return !$interval || $interval->isEmpty() ? null : $interval; + } + + protected function resolveInterval($interval) + { + if (!($interval instanceof self)) { + return self::make($interval); + } + + return $interval; + } + + /** + * Sets up a DateInterval from the relative parts of the string. + * + * @param string $time + * + * @return static + * + * @link https://php.net/manual/en/dateinterval.createfromdatestring.php + */ + #[ReturnTypeWillChange] + public static function createFromDateString($time) + { + $interval = @parent::createFromDateString(strtr($time, [ + ',' => ' ', + ' and ' => ' ', + ])); + + if ($interval instanceof DateInterval) { + $interval = static::instance($interval); + } + + return $interval; + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return int|float|string + */ + public function get($name) + { + if (str_starts_with($name, 'total')) { + return $this->total(substr($name, 5)); + } + + switch ($name) { + case 'years': + return $this->y; + + case 'months': + return $this->m; + + case 'dayz': + return $this->d; + + case 'hours': + return $this->h; + + case 'minutes': + return $this->i; + + case 'seconds': + return $this->s; + + case 'milli': + case 'milliseconds': + return (int) (round($this->f * Carbon::MICROSECONDS_PER_SECOND) / Carbon::MICROSECONDS_PER_MILLISECOND); + + case 'micro': + case 'microseconds': + return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND); + + case 'microExcludeMilli': + return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND) % Carbon::MICROSECONDS_PER_MILLISECOND; + + case 'weeks': + return (int) ($this->d / (int) static::getDaysPerWeek()); + + case 'daysExcludeWeeks': + case 'dayzExcludeWeeks': + return $this->d % (int) static::getDaysPerWeek(); + + case 'locale': + return $this->getTranslatorLocale(); + + default: + throw new UnknownGetterException($name); + } + } + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return int|float|string + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string|array $name + * @param int $value + * + * @throws UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null) + { + $properties = \is_array($name) ? $name : [$name => $value]; + + foreach ($properties as $key => $value) { + switch (Carbon::singularUnit(rtrim($key, 'z'))) { + case 'year': + $this->checkIntegerValue($key, $value); + $this->y = $value; + $this->handleDecimalPart('year', $value, $this->y); + + break; + + case 'month': + $this->checkIntegerValue($key, $value); + $this->m = $value; + $this->handleDecimalPart('month', $value, $this->m); + + break; + + case 'week': + $this->checkIntegerValue($key, $value); + $days = $value * (int) static::getDaysPerWeek(); + $this->assertSafeForInteger('days total (including weeks)', $days); + $this->d = $days; + $this->handleDecimalPart('day', $days, $this->d); + + break; + + case 'day': + $this->checkIntegerValue($key, $value); + $this->d = $value; + $this->handleDecimalPart('day', $value, $this->d); + + break; + + case 'daysexcludeweek': + case 'dayzexcludeweek': + $this->checkIntegerValue($key, $value); + $days = $this->weeks * (int) static::getDaysPerWeek() + $value; + $this->assertSafeForInteger('days total (including weeks)', $days); + $this->d = $days; + $this->handleDecimalPart('day', $days, $this->d); + + break; + + case 'hour': + $this->checkIntegerValue($key, $value); + $this->h = $value; + $this->handleDecimalPart('hour', $value, $this->h); + + break; + + case 'minute': + $this->checkIntegerValue($key, $value); + $this->i = $value; + $this->handleDecimalPart('minute', $value, $this->i); + + break; + + case 'second': + $this->checkIntegerValue($key, $value); + $this->s = $value; + $this->handleDecimalPart('second', $value, $this->s); + + break; + + case 'milli': + case 'millisecond': + $this->microseconds = $value * Carbon::MICROSECONDS_PER_MILLISECOND + $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND; + + break; + + case 'micro': + case 'microsecond': + $this->f = $value / Carbon::MICROSECONDS_PER_SECOND; + + break; + + default: + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new UnknownSetterException($key); + } + + $this->$key = $value; + } + } + + return $this; + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string $name + * @param int $value + * + * @throws UnknownSetterException + */ + public function __set($name, $value) + { + $this->set($name, $value); + } + + /** + * Allow setting of weeks and days to be cumulative. + * + * @param int $weeks Number of weeks to set + * @param int $days Number of days to set + * + * @return static + */ + public function weeksAndDays($weeks, $days) + { + $this->dayz = ($weeks * static::getDaysPerWeek()) + $days; + + return $this; + } + + /** + * Returns true if the interval is empty for each unit. + * + * @return bool + */ + public function isEmpty() + { + return $this->years === 0 && + $this->months === 0 && + $this->dayz === 0 && + !$this->days && + $this->hours === 0 && + $this->minutes === 0 && + $this->seconds === 0 && + $this->microseconds === 0; + } + + /** + * Register a custom macro. + * + * @example + * ``` + * CarbonInterval::macro('twice', function () { + * return $this->times(2); + * }); + * echo CarbonInterval::hours(2)->twice(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Register macros from a mixin object. + * + * @example + * ``` + * CarbonInterval::mixin(new class { + * public function daysToHours() { + * return function () { + * $this->hours += $this->days; + * $this->days = 0; + * + * return $this; + * }; + * } + * public function hoursToDays() { + * return function () { + * $this->days += $this->hours; + * $this->hours = 0; + * + * return $this; + * }; + * } + * }); + * echo CarbonInterval::hours(5)->hoursToDays() . "\n"; + * echo CarbonInterval::days(5)->daysToHours() . "\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + static::baseMixin($mixin); + } + + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + /** + * Allow fluent calls on the setters... CarbonInterval::years(3)->months(5)->day(). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadFluentSetterException|Throwable + * + * @return static + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + return $this->callMacro($method, $parameters); + }); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + if (preg_match('/^(?add|sub)(?[A-Z].*)$/', $method, $match)) { + $value = $this->getMagicParameter($parameters, 0, Carbon::pluralUnit($match['unit']), 0); + + return $this->{$match['method']}($value, $match['unit']); + } + + $value = $this->getMagicParameter($parameters, 0, Carbon::pluralUnit($method), 1); + + try { + $this->set($method, $value); + } catch (UnknownSetterException $exception) { + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new BadFluentSetterException($method, 0, $exception); + } + } + + return $this; + } + + protected function getForHumansInitialVariables($syntax, $short) + { + if (\is_array($syntax)) { + return $syntax; + } + + if (\is_int($short)) { + return [ + 'parts' => $short, + 'short' => false, + ]; + } + + if (\is_bool($syntax)) { + return [ + 'short' => $syntax, + 'syntax' => CarbonInterface::DIFF_ABSOLUTE, + ]; + } + + return []; + } + + /** + * @param mixed $syntax + * @param mixed $short + * @param mixed $parts + * @param mixed $options + * + * @return array + */ + protected function getForHumansParameters($syntax = null, $short = false, $parts = -1, $options = null) + { + $optionalSpace = ' '; + $default = $this->getTranslationMessage('list.0') ?? $this->getTranslationMessage('list') ?? ' '; + $join = $default === '' ? '' : ' '; + $altNumbers = false; + $aUnit = false; + $minimumUnit = 's'; + $skip = []; + extract($this->getForHumansInitialVariables($syntax, $short)); + $skip = array_map('strtolower', array_filter((array) $skip, static function ($value) { + return \is_string($value) && $value !== ''; + })); + + if ($syntax === null) { + $syntax = CarbonInterface::DIFF_ABSOLUTE; + } + + if ($parts === -1) { + $parts = INF; + } + + if ($options === null) { + $options = static::getHumanDiffOptions(); + } + + if ($join === false) { + $join = ' '; + } elseif ($join === true) { + $join = [ + $default, + $this->getTranslationMessage('list.1') ?? $default, + ]; + } + + if ($altNumbers && $altNumbers !== true) { + $language = new Language($this->locale); + $altNumbers = \in_array($language->getCode(), (array) $altNumbers, true); + } + + if (\is_array($join)) { + [$default, $last] = $join; + + if ($default !== ' ') { + $optionalSpace = ''; + } + + $join = function ($list) use ($default, $last) { + if (\count($list) < 2) { + return implode('', $list); + } + + $end = array_pop($list); + + return implode($default, $list).$last.$end; + }; + } + + if (\is_string($join)) { + if ($join !== ' ') { + $optionalSpace = ''; + } + + $glue = $join; + $join = function ($list) use ($glue) { + return implode($glue, $list); + }; + } + + $interpolations = [ + ':optional-space' => $optionalSpace, + ]; + + return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip]; + } + + protected static function getRoundingMethodFromOptions(int $options): ?string + { + if ($options & CarbonInterface::ROUND) { + return 'round'; + } + + if ($options & CarbonInterface::CEIL) { + return 'ceil'; + } + + if ($options & CarbonInterface::FLOOR) { + return 'floor'; + } + + return null; + } + + /** + * Returns interval values as an array where key are the unit names and values the counts. + * + * @return int[] + */ + public function toArray() + { + return [ + 'years' => $this->years, + 'months' => $this->months, + 'weeks' => $this->weeks, + 'days' => $this->daysExcludeWeeks, + 'hours' => $this->hours, + 'minutes' => $this->minutes, + 'seconds' => $this->seconds, + 'microseconds' => $this->microseconds, + ]; + } + + /** + * Returns interval non-zero values as an array where key are the unit names and values the counts. + * + * @return int[] + */ + public function getNonZeroValues() + { + return array_filter($this->toArray(), 'intval'); + } + + /** + * Returns interval values as an array where key are the unit names and values the counts + * from the biggest non-zero one the the smallest non-zero one. + * + * @return int[] + */ + public function getValuesSequence() + { + $nonZeroValues = $this->getNonZeroValues(); + + if ($nonZeroValues === []) { + return []; + } + + $keys = array_keys($nonZeroValues); + $firstKey = $keys[0]; + $lastKey = $keys[\count($keys) - 1]; + $values = []; + $record = false; + + foreach ($this->toArray() as $unit => $count) { + if ($unit === $firstKey) { + $record = true; + } + + if ($record) { + $values[$unit] = $count; + } + + if ($unit === $lastKey) { + $record = false; + } + } + + return $values; + } + + /** + * Get the current interval in a human readable format in the current locale. + * + * @example + * ``` + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans() . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 2]) . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['short' => true]) . "\n"; + * echo CarbonInterval::fromString('1d 24h')->forHumans(['join' => ' or ']) . "\n"; + * echo CarbonInterval::fromString('1d 24h')->forHumans(['minimumUnit' => 'hour']) . "\n"; + * ``` + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: -1: no limits) + * @param int $options human diff options + * + * @throws Exception + * + * @return string + */ + public function forHumans($syntax = null, $short = false, $parts = -1, $options = null) + { + [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip] = $this + ->getForHumansParameters($syntax, $short, $parts, $options); + + $interval = []; + + $syntax = (int) ($syntax ?? CarbonInterface::DIFF_ABSOLUTE); + $absolute = $syntax === CarbonInterface::DIFF_ABSOLUTE; + $relativeToNow = $syntax === CarbonInterface::DIFF_RELATIVE_TO_NOW; + $count = 1; + $unit = $short ? 's' : 'second'; + $isFuture = $this->invert === 1; + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + $declensionMode = null; + + /** @var \EDD\Vendor\Symfony\Component\Translation\Translator $translator */ + $translator = $this->getLocalTranslator(); + + $handleDeclensions = function ($unit, $count, $index = 0, $parts = 1) use ($interpolations, $transId, $translator, $altNumbers, $absolute, &$declensionMode) { + if (!$absolute) { + $declensionMode = $declensionMode ?? $this->translate($transId.'_mode'); + + if ($this->needsDeclension($declensionMode, $index, $parts)) { + // Some languages have special pluralization for past and future tense. + $key = $unit.'_'.$transId; + $result = $this->translate($key, $interpolations, $count, $translator, $altNumbers); + + if ($result !== $key) { + return $result; + } + } + } + + $result = $this->translate($unit, $interpolations, $count, $translator, $altNumbers); + + if ($result !== $unit) { + return $result; + } + + return null; + }; + + $intervalValues = $this; + $method = static::getRoundingMethodFromOptions($options); + + if ($method) { + $previousCount = INF; + + while ( + \count($intervalValues->getNonZeroValues()) > $parts && + ($count = \count($keys = array_keys($intervalValues->getValuesSequence()))) > 1 + ) { + $index = min($count, $previousCount - 1) - 2; + + if ($index < 0) { + break; + } + + $intervalValues = $this->copy()->roundUnit( + $keys[$index], + 1, + $method + ); + $previousCount = $count; + } + } + + $diffIntervalArray = [ + ['value' => $intervalValues->years, 'unit' => 'year', 'unitShort' => 'y'], + ['value' => $intervalValues->months, 'unit' => 'month', 'unitShort' => 'm'], + ['value' => $intervalValues->weeks, 'unit' => 'week', 'unitShort' => 'w'], + ['value' => $intervalValues->daysExcludeWeeks, 'unit' => 'day', 'unitShort' => 'd'], + ['value' => $intervalValues->hours, 'unit' => 'hour', 'unitShort' => 'h'], + ['value' => $intervalValues->minutes, 'unit' => 'minute', 'unitShort' => 'min'], + ['value' => $intervalValues->seconds, 'unit' => 'second', 'unitShort' => 's'], + ['value' => $intervalValues->milliseconds, 'unit' => 'millisecond', 'unitShort' => 'ms'], + ['value' => $intervalValues->microExcludeMilli, 'unit' => 'microsecond', 'unitShort' => 'µs'], + ]; + + if (!empty($skip)) { + foreach ($diffIntervalArray as $index => &$unitData) { + $nextIndex = $index + 1; + + if ($unitData['value'] && + isset($diffIntervalArray[$nextIndex]) && + \count(array_intersect([$unitData['unit'], $unitData['unit'].'s', $unitData['unitShort']], $skip)) + ) { + $diffIntervalArray[$nextIndex]['value'] += $unitData['value'] * + self::getFactorWithDefault($diffIntervalArray[$nextIndex]['unit'], $unitData['unit']); + $unitData['value'] = 0; + } + } + } + + $transChoice = function ($short, $unitData, $index, $parts) use ($absolute, $handleDeclensions, $translator, $aUnit, $altNumbers, $interpolations) { + $count = $unitData['value']; + + if ($short) { + $result = $handleDeclensions($unitData['unitShort'], $count, $index, $parts); + + if ($result !== null) { + return $result; + } + } elseif ($aUnit) { + $result = $handleDeclensions('a_'.$unitData['unit'], $count, $index, $parts); + + if ($result !== null) { + return $result; + } + } + + if (!$absolute) { + return $handleDeclensions($unitData['unit'], $count, $index, $parts); + } + + return $this->translate($unitData['unit'], $interpolations, $count, $translator, $altNumbers); + }; + + $fallbackUnit = ['second', 's']; + + foreach ($diffIntervalArray as $diffIntervalData) { + if ($diffIntervalData['value'] > 0) { + $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit']; + $count = $diffIntervalData['value']; + $interval[] = [$short, $diffIntervalData]; + } elseif ($options & CarbonInterface::SEQUENTIAL_PARTS_ONLY && \count($interval) > 0) { + break; + } + + // break the loop after we get the required number of parts in array + if (\count($interval) >= $parts) { + break; + } + + // break the loop after we have reached the minimum unit + if (\in_array($minimumUnit, [$diffIntervalData['unit'], $diffIntervalData['unitShort']], true)) { + $fallbackUnit = [$diffIntervalData['unit'], $diffIntervalData['unitShort']]; + + break; + } + } + + $actualParts = \count($interval); + + foreach ($interval as $index => &$item) { + $item = $transChoice($item[0], $item[1], $index, $actualParts); + } + + if (\count($interval) === 0) { + if ($relativeToNow && $options & CarbonInterface::JUST_NOW) { + $key = 'diff_now'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + + $count = $options & CarbonInterface::NO_ZERO_DIFF ? 1 : 0; + $unit = $fallbackUnit[$short ? 1 : 0]; + $interval[] = $this->translate($unit, $interpolations, $count, $translator, $altNumbers); + } + + // join the interval parts by a space + $time = $join($interval); + + unset($diffIntervalArray, $interval); + + if ($absolute) { + return $time; + } + + $isFuture = $this->invert === 1; + + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + + if ($parts === 1) { + if ($relativeToNow && $unit === 'day') { + if ($count === 1 && $options & CarbonInterface::ONE_DAY_WORDS) { + $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + + if ($count === 2 && $options & CarbonInterface::TWO_DAY_WORDS) { + $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + } + + $aTime = $aUnit ? $handleDeclensions('a_'.$unit, $count) : null; + + $time = $aTime ?: $handleDeclensions($unit, $count) ?: $time; + } + + $time = [':time' => $time]; + + return $this->translate($transId, array_merge($time, $interpolations, $time), null, $translator); + } + + /** + * Format the instance as a string using the forHumans() function. + * + * @throws Exception + * + * @return string + */ + public function __toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + if (!$format) { + return $this->forHumans(); + } + + if ($format instanceof Closure) { + return $format($this); + } + + return $this->format($format); + } + + /** + * Return native DateInterval PHP object matching the current instance. + * + * @example + * ``` + * var_dump(CarbonInterval::hours(2)->toDateInterval()); + * ``` + * + * @return DateInterval + */ + public function toDateInterval() + { + return self::castIntervalToClass($this, DateInterval::class); + } + + /** + * Convert the interval to a CarbonPeriod. + * + * @param DateTimeInterface|string|int ...$params Start date, [end date or recurrences] and optional settings. + * + * @return CarbonPeriod + */ + public function toPeriod(...$params) + { + if ($this->tzName) { + $tz = \is_string($this->tzName) ? new DateTimeZone($this->tzName) : $this->tzName; + + if ($tz instanceof DateTimeZone) { + array_unshift($params, $tz); + } + } + + return CarbonPeriod::create($this, ...$params); + } + + /** + * Invert the interval. + * + * @param bool|int $inverted if a parameter is passed, the passed value cast as 1 or 0 is used + * as the new value of the ->invert property. + * + * @return $this + */ + public function invert($inverted = null) + { + $this->invert = (\func_num_args() === 0 ? !$this->invert : $inverted) ? 1 : 0; + + return $this; + } + + protected function solveNegativeInterval() + { + if (!$this->isEmpty() && $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0 && $this->microseconds <= 0) { + $this->years *= -1; + $this->months *= -1; + $this->dayz *= -1; + $this->hours *= -1; + $this->minutes *= -1; + $this->seconds *= -1; + $this->microseconds *= -1; + $this->invert(); + } + + return $this; + } + + /** + * Add the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function add($unit, $value = 1) + { + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + if (\is_string($unit) && !preg_match('/^\s*\d/', $unit)) { + $unit = "$value $unit"; + $value = 1; + } + + $interval = static::make($unit); + + if (!$interval) { + throw new InvalidIntervalException('This type of data cannot be added/subtracted.'); + } + + if ($value !== 1) { + $interval->times($value); + } + + $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1; + $this->years += $interval->y * $sign; + $this->months += $interval->m * $sign; + $this->dayz += ($interval->days === false ? $interval->d : $interval->days) * $sign; + $this->hours += $interval->h * $sign; + $this->minutes += $interval->i * $sign; + $this->seconds += $interval->s * $sign; + $this->microseconds += $interval->microseconds * $sign; + + $this->solveNegativeInterval(); + + return $this; + } + + /** + * Subtract the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function sub($unit, $value = 1) + { + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->add($unit, -(float) $value); + } + + /** + * Subtract the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function subtract($unit, $value = 1) + { + return $this->sub($unit, $value); + } + + /** + * Add given parameters to the current interval. + * + * @param int $years + * @param int $months + * @param int|float $weeks + * @param int|float $days + * @param int|float $hours + * @param int|float $minutes + * @param int|float $seconds + * @param int|float $microseconds + * + * @return $this + */ + public function plus( + $years = 0, + $months = 0, + $weeks = 0, + $days = 0, + $hours = 0, + $minutes = 0, + $seconds = 0, + $microseconds = 0 + ): self { + return $this->add(" + $years years $months months $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Add given parameters to the current interval. + * + * @param int $years + * @param int $months + * @param int|float $weeks + * @param int|float $days + * @param int|float $hours + * @param int|float $minutes + * @param int|float $seconds + * @param int|float $microseconds + * + * @return $this + */ + public function minus( + $years = 0, + $months = 0, + $weeks = 0, + $days = 0, + $hours = 0, + $minutes = 0, + $seconds = 0, + $microseconds = 0 + ): self { + return $this->sub(" + $years years $months months $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Multiply current instance given number of times. times() is naive, it multiplies each unit + * (so day can be greater than 31, hour can be greater than 23, etc.) and the result is rounded + * separately for each unit. + * + * Use times() when you want a fast and approximated calculation that does not cascade units. + * + * For a precise and cascaded calculation, + * + * @see multiply() + * + * @param float|int $factor + * + * @return $this + */ + public function times($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + + $this->years = (int) round($this->years * $factor); + $this->months = (int) round($this->months * $factor); + $this->dayz = (int) round($this->dayz * $factor); + $this->hours = (int) round($this->hours * $factor); + $this->minutes = (int) round($this->minutes * $factor); + $this->seconds = (int) round($this->seconds * $factor); + $this->microseconds = (int) round($this->microseconds * $factor); + + return $this; + } + + /** + * Divide current instance by a given divider. shares() is naive, it divides each unit separately + * and the result is rounded for each unit. So 5 hours and 20 minutes shared by 3 becomes 2 hours + * and 7 minutes. + * + * Use shares() when you want a fast and approximated calculation that does not cascade units. + * + * For a precise and cascaded calculation, + * + * @see divide() + * + * @param float|int $divider + * + * @return $this + */ + public function shares($divider) + { + return $this->times(1 / $divider); + } + + protected function copyProperties(self $interval, $ignoreSign = false) + { + $this->years = $interval->years; + $this->months = $interval->months; + $this->dayz = $interval->dayz; + $this->hours = $interval->hours; + $this->minutes = $interval->minutes; + $this->seconds = $interval->seconds; + $this->microseconds = $interval->microseconds; + + if (!$ignoreSign) { + $this->invert = $interval->invert; + } + + return $this; + } + + /** + * Multiply and cascade current instance by a given factor. + * + * @param float|int $factor + * + * @return $this + */ + public function multiply($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + + $yearPart = (int) floor($this->years * $factor); // Split calculation to prevent imprecision + + if ($yearPart) { + $this->years -= $yearPart / $factor; + } + + return $this->copyProperties( + static::create($yearPart) + ->microseconds(abs($this->totalMicroseconds) * $factor) + ->cascade(), + true + ); + } + + /** + * Divide and cascade current instance by a given divider. + * + * @param float|int $divider + * + * @return $this + */ + public function divide($divider) + { + return $this->multiply(1 / $divider); + } + + /** + * Get the interval_spec string of a date interval. + * + * @param DateInterval $interval + * + * @return string + */ + public static function getDateIntervalSpec(DateInterval $interval, bool $microseconds = false, array $skip = []) + { + $date = array_filter([ + static::PERIOD_YEARS => abs($interval->y), + static::PERIOD_MONTHS => abs($interval->m), + static::PERIOD_DAYS => abs($interval->d), + ]); + + if ( + $interval->days >= CarbonInterface::DAYS_PER_WEEK * CarbonInterface::WEEKS_PER_MONTH && + (!isset($date[static::PERIOD_YEARS]) || \count(array_intersect(['y', 'year', 'years'], $skip))) && + (!isset($date[static::PERIOD_MONTHS]) || \count(array_intersect(['m', 'month', 'months'], $skip))) + ) { + $date = [ + static::PERIOD_DAYS => abs($interval->days), + ]; + } + + $seconds = abs($interval->s); + if ($microseconds && $interval->f > 0) { + $seconds = sprintf('%d.%06d', $seconds, abs($interval->f) * 1000000); + } + + $time = array_filter([ + static::PERIOD_HOURS => abs($interval->h), + static::PERIOD_MINUTES => abs($interval->i), + static::PERIOD_SECONDS => $seconds, + ]); + + $specString = static::PERIOD_PREFIX; + + foreach ($date as $key => $value) { + $specString .= $value.$key; + } + + if (\count($time) > 0) { + $specString .= static::PERIOD_TIME_PREFIX; + foreach ($time as $key => $value) { + $specString .= $value.$key; + } + } + + return $specString === static::PERIOD_PREFIX ? 'PT0S' : $specString; + } + + /** + * Get the interval_spec string. + * + * @return string + */ + public function spec(bool $microseconds = false) + { + return static::getDateIntervalSpec($this, $microseconds); + } + + /** + * Comparing 2 date intervals. + * + * @param DateInterval $first + * @param DateInterval $second + * + * @return int + */ + public static function compareDateIntervals(DateInterval $first, DateInterval $second) + { + $current = Carbon::now(); + $passed = $current->avoidMutation()->add($second); + $current->add($first); + + if ($current < $passed) { + return -1; + } + if ($current > $passed) { + return 1; + } + + return 0; + } + + /** + * Comparing with passed interval. + * + * @param DateInterval $interval + * + * @return int + */ + public function compare(DateInterval $interval) + { + return static::compareDateIntervals($this, $interval); + } + + private function invertCascade(array $values) + { + return $this->set(array_map(function ($value) { + return -$value; + }, $values))->doCascade(true)->invert(); + } + + private function doCascade(bool $deep) + { + $originalData = $this->toArray(); + $originalData['milliseconds'] = (int) ($originalData['microseconds'] / static::getMicrosecondsPerMillisecond()); + $originalData['microseconds'] = $originalData['microseconds'] % static::getMicrosecondsPerMillisecond(); + $originalData['weeks'] = (int) ($this->d / static::getDaysPerWeek()); + $originalData['daysExcludeWeeks'] = fmod($this->d, static::getDaysPerWeek()); + unset($originalData['days']); + $newData = $originalData; + $previous = []; + + foreach (self::getFlipCascadeFactors() as $source => [$target, $factor]) { + foreach (['source', 'target'] as $key) { + if ($$key === 'dayz') { + $$key = 'daysExcludeWeeks'; + } + } + + $value = $newData[$source]; + $modulo = fmod($factor + fmod($value, $factor), $factor); + $newData[$source] = $modulo; + $newData[$target] += ($value - $modulo) / $factor; + + $decimalPart = fmod($newData[$source], 1); + + if ($decimalPart !== 0.0) { + $unit = $source; + + foreach ($previous as [$subUnit, $subFactor]) { + $newData[$unit] -= $decimalPart; + $newData[$subUnit] += $decimalPart * $subFactor; + $decimalPart = fmod($newData[$subUnit], 1); + + if ($decimalPart === 0.0) { + break; + } + + $unit = $subUnit; + } + } + + array_unshift($previous, [$source, $factor]); + } + + $positive = null; + + if (!$deep) { + foreach ($newData as $value) { + if ($value) { + if ($positive === null) { + $positive = ($value > 0); + + continue; + } + + if (($value > 0) !== $positive) { + return $this->invertCascade($originalData) + ->solveNegativeInterval(); + } + } + } + } + + return $this->set($newData) + ->solveNegativeInterval(); + } + + /** + * Convert overflowed values into bigger units. + * + * @return $this + */ + public function cascade() + { + return $this->doCascade(false); + } + + public function hasNegativeValues(): bool + { + foreach ($this->toArray() as $value) { + if ($value < 0) { + return true; + } + } + + return false; + } + + public function hasPositiveValues(): bool + { + foreach ($this->toArray() as $value) { + if ($value > 0) { + return true; + } + } + + return false; + } + + /** + * Get amount of given unit equivalent to the interval. + * + * @param string $unit + * + * @throws UnknownUnitException|UnitNotConfiguredException + * + * @return float + */ + public function total($unit) + { + $realUnit = $unit = strtolower($unit); + + if (\in_array($unit, ['days', 'weeks'])) { + $realUnit = 'dayz'; + } elseif (!\in_array($unit, ['microseconds', 'milliseconds', 'seconds', 'minutes', 'hours', 'dayz', 'months', 'years'])) { + throw new UnknownUnitException($unit); + } + + $result = 0; + $cumulativeFactor = 0; + $unitFound = false; + $factors = self::getFlipCascadeFactors(); + $daysPerWeek = (int) static::getDaysPerWeek(); + + $values = [ + 'years' => $this->years, + 'months' => $this->months, + 'weeks' => (int) ($this->d / $daysPerWeek), + 'dayz' => fmod($this->d, $daysPerWeek), + 'hours' => $this->hours, + 'minutes' => $this->minutes, + 'seconds' => $this->seconds, + 'milliseconds' => (int) ($this->microseconds / Carbon::MICROSECONDS_PER_MILLISECOND), + 'microseconds' => $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND, + ]; + + if (isset($factors['dayz']) && $factors['dayz'][0] !== 'weeks') { + $values['dayz'] += $values['weeks'] * $daysPerWeek; + $values['weeks'] = 0; + } + + foreach ($factors as $source => [$target, $factor]) { + if ($source === $realUnit) { + $unitFound = true; + $value = $values[$source]; + $result += $value; + $cumulativeFactor = 1; + } + + if ($factor === false) { + if ($unitFound) { + break; + } + + $result = 0; + $cumulativeFactor = 0; + + continue; + } + + if ($target === $realUnit) { + $unitFound = true; + } + + if ($cumulativeFactor) { + $cumulativeFactor *= $factor; + $result += $values[$target] * $cumulativeFactor; + + continue; + } + + $value = $values[$source]; + + $result = ($result + $value) / $factor; + } + + if (isset($target) && !$cumulativeFactor) { + $result += $values[$target]; + } + + if (!$unitFound) { + throw new UnitNotConfiguredException($unit); + } + + if ($this->invert) { + $result *= -1; + } + + if ($unit === 'weeks') { + $result /= $daysPerWeek; + } + + // Cast as int numbers with no decimal part + return fmod($result, 1) === 0.0 ? (int) $result : $result; + } + + /** + * Determines if the instance is equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see equalTo() + * + * @return bool + */ + public function eq($interval): bool + { + return $this->equalTo($interval); + } + + /** + * Determines if the instance is equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function equalTo($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval !== null && $this->totalMicroseconds === $interval->totalMicroseconds; + } + + /** + * Determines if the instance is not equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($interval): bool + { + return $this->notEqualTo($interval); + } + + /** + * Determines if the instance is not equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function notEqualTo($interval): bool + { + return !$this->eq($interval); + } + + /** + * Determines if the instance is greater (longer) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see greaterThan() + * + * @return bool + */ + public function gt($interval): bool + { + return $this->greaterThan($interval); + } + + /** + * Determines if the instance is greater (longer) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function greaterThan($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval === null || $this->totalMicroseconds > $interval->totalMicroseconds; + } + + /** + * Determines if the instance is greater (longer) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($interval): bool + { + return $this->greaterThanOrEqualTo($interval); + } + + /** + * Determines if the instance is greater (longer) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function greaterThanOrEqualTo($interval): bool + { + return $this->greaterThan($interval) || $this->equalTo($interval); + } + + /** + * Determines if the instance is less (shorter) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see lessThan() + * + * @return bool + */ + public function lt($interval): bool + { + return $this->lessThan($interval); + } + + /** + * Determines if the instance is less (shorter) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function lessThan($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval !== null && $this->totalMicroseconds < $interval->totalMicroseconds; + } + + /** + * Determines if the instance is less (shorter) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($interval): bool + { + return $this->lessThanOrEqualTo($interval); + } + + /** + * Determines if the instance is less (shorter) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function lessThanOrEqualTo($interval): bool + { + return $this->lessThan($interval) || $this->equalTo($interval); + } + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2)); // true + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2), false); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($interval1, $interval2, $equal = true): bool + { + return $equal + ? $this->greaterThanOrEqualTo($interval1) && $this->lessThanOrEqualTo($interval2) + : $this->greaterThan($interval1) && $this->lessThan($interval2); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // true + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * + * @return bool + */ + public function betweenIncluded($interval1, $interval2): bool + { + return $this->between($interval1, $interval2, true); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * + * @return bool + */ + public function betweenExcluded($interval1, $interval2): bool + { + return $this->between($interval1, $interval2, false); + } + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2)); // true + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2), false); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($interval1, $interval2, $equal = true): bool + { + return $this->between($interval1, $interval2, $equal); + } + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * @param string $function + * + * @throws Exception + * + * @return $this + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + if (static::getCascadeFactors() !== static::getDefaultCascadeFactors()) { + $value = $function($this->total($unit) / $precision) * $precision; + $inverted = $value < 0; + + return $this->copyProperties(self::fromString( + number_format(abs($value), 12, '.', '').' '.$unit + )->invert($inverted)->cascade()); + } + + $base = CarbonImmutable::parse('2000-01-01 00:00:00', 'UTC') + ->roundUnit($unit, $precision, $function); + $next = $base->add($this); + $inverted = $next < $base; + + if ($inverted) { + $next = $base->sub($this); + } + + $this->copyProperties( + $next + ->roundUnit($unit, $precision, $function) + ->diffAsCarbonInterval($base) + ); + + return $this->invert($inverted); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * @param string $function + * + * @throws Exception + * + * @return $this + */ + public function round($precision = 1, $function = 'round') + { + return $this->roundWith($precision, $function); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function floor($precision = 1) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function ceil($precision = 1) + { + return $this->round($precision, 'ceil'); + } + + private function needsDeclension(string $mode, int $index, int $parts): bool + { + switch ($mode) { + case 'last': + return $index === $parts - 1; + default: + return true; + } + } + + private function checkIntegerValue(string $name, $value) + { + if (\is_int($value)) { + return; + } + + $this->assertSafeForInteger($name, $value); + + if (\is_float($value) && (((float) (int) $value) === $value)) { + return; + } + + if (!self::$floatSettersEnabled) { + $type = \gettype($value); + @trigger_error( + "Since 2.70.0, it's deprecated to pass $type value for $name.\n". + "It's truncated when stored as an integer interval unit.\n". + "From 3.0.0, decimal part will no longer be truncated and will be cascaded to smaller units.\n". + "- To maintain the current behavior, use explicit cast: $name((int) \$value)\n". + "- To adopt the new behavior globally, call CarbonInterval::enableFloatSetters()\n", + \E_USER_DEPRECATED + ); + } + } + + /** + * Throw an exception if precision loss when storing the given value as an integer would be >= 1.0. + */ + private function assertSafeForInteger(string $name, $value) + { + if ($value && !\is_int($value) && ($value >= 0x7fffffffffffffff || $value <= -0x7fffffffffffffff)) { + throw new OutOfRangeException($name, -0x7fffffffffffffff, 0x7fffffffffffffff, $value); + } + } + + private function handleDecimalPart(string $unit, $value, $integerValue) + { + if (self::$floatSettersEnabled) { + $floatValue = (float) $value; + $base = (float) $integerValue; + + if ($floatValue === $base) { + return; + } + + $units = [ + 'y' => 'year', + 'm' => 'month', + 'd' => 'day', + 'h' => 'hour', + 'i' => 'minute', + 's' => 'second', + ]; + $upper = true; + + foreach ($units as $property => $name) { + if ($name === $unit) { + $upper = false; + + continue; + } + + if (!$upper && $this->$property !== 0) { + throw new RuntimeException( + "You cannot set $unit to a float value as $name would be overridden, ". + 'set it first to 0 explicitly if you really want to erase its value' + ); + } + } + + $this->add($unit, $floatValue - $base); + } + } +} diff --git a/libraries/Carbon/src/Carbon/CarbonPeriod.php b/libraries/Carbon/src/Carbon/CarbonPeriod.php new file mode 100644 index 00000000000..e753ce7a7be --- /dev/null +++ b/libraries/Carbon/src/Carbon/CarbonPeriod.php @@ -0,0 +1,2742 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Carbon\Exceptions\EndLessPeriodException; +use EDD\Vendor\Carbon\Exceptions\InvalidCastException; +use EDD\Vendor\Carbon\Exceptions\InvalidIntervalException; +use EDD\Vendor\Carbon\Exceptions\InvalidPeriodDateException; +use EDD\Vendor\Carbon\Exceptions\InvalidPeriodParameterException; +use EDD\Vendor\Carbon\Exceptions\NotACarbonClassException; +use EDD\Vendor\Carbon\Exceptions\NotAPeriodException; +use EDD\Vendor\Carbon\Exceptions\UnknownGetterException; +use EDD\Vendor\Carbon\Exceptions\UnknownMethodException; +use EDD\Vendor\Carbon\Exceptions\UnreachableException; +use EDD\Vendor\Carbon\Traits\IntervalRounding; +use EDD\Vendor\Carbon\Traits\Mixin; +use EDD\Vendor\Carbon\Traits\Options; +use EDD\Vendor\Carbon\Traits\ToStringFormat; +use Closure; +use Countable; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use Iterator; +use JsonSerializable; +use ReflectionException; +use ReturnTypeWillChange; +use RuntimeException; + +/** + * Substitution of DatePeriod with some modifications and many more features. + * + * @property-read int|float $recurrences number of recurrences (if end not set). + * @property-read bool $include_start_date rather the start date is included in the iteration. + * @property-read bool $include_end_date rather the end date is included in the iteration (if recurrences not set). + * @property-read CarbonInterface $start Period start date. + * @property-read CarbonInterface $current Current date from the iteration. + * @property-read CarbonInterface $end Period end date. + * @property-read CarbonInterval $interval Underlying date interval instance. Always present, one day by default. + * + * @method static static start($date, $inclusive = null) Create instance specifying start date or modify the start date if called on an instance. + * @method static static since($date, $inclusive = null) Alias for start(). + * @method static static sinceNow($inclusive = null) Create instance with start date set to now or set the start date to now if called on an instance. + * @method static static end($date = null, $inclusive = null) Create instance specifying end date or modify the end date if called on an instance. + * @method static static until($date = null, $inclusive = null) Alias for end(). + * @method static static untilNow($inclusive = null) Create instance with end date set to now or set the end date to now if called on an instance. + * @method static static dates($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance. + * @method static static between($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance. + * @method static static recurrences($recurrences = null) Create instance with maximum number of recurrences or modify the number of recurrences if called on an instance. + * @method static static times($recurrences = null) Alias for recurrences(). + * @method static static options($options = null) Create instance with options or modify the options if called on an instance. + * @method static static toggle($options, $state = null) Create instance with options toggled on or off, or toggle options if called on an instance. + * @method static static filter($callback, $name = null) Create instance with filter added to the stack or append a filter if called on an instance. + * @method static static push($callback, $name = null) Alias for filter(). + * @method static static prepend($callback, $name = null) Create instance with filter prepended to the stack or prepend a filter if called on an instance. + * @method static static filters(array $filters = []) Create instance with filters stack or replace the whole filters stack if called on an instance. + * @method static static interval($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static each($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static every($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static step($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static stepBy($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static invert() Create instance with inverted date interval or invert the interval if called on an instance. + * @method static static years($years = 1) Create instance specifying a number of years for date interval or replace the interval by the given a number of years if called on an instance. + * @method static static year($years = 1) Alias for years(). + * @method static static months($months = 1) Create instance specifying a number of months for date interval or replace the interval by the given a number of months if called on an instance. + * @method static static month($months = 1) Alias for months(). + * @method static static weeks($weeks = 1) Create instance specifying a number of weeks for date interval or replace the interval by the given a number of weeks if called on an instance. + * @method static static week($weeks = 1) Alias for weeks(). + * @method static static days($days = 1) Create instance specifying a number of days for date interval or replace the interval by the given a number of days if called on an instance. + * @method static static dayz($days = 1) Alias for days(). + * @method static static day($days = 1) Alias for days(). + * @method static static hours($hours = 1) Create instance specifying a number of hours for date interval or replace the interval by the given a number of hours if called on an instance. + * @method static static hour($hours = 1) Alias for hours(). + * @method static static minutes($minutes = 1) Create instance specifying a number of minutes for date interval or replace the interval by the given a number of minutes if called on an instance. + * @method static static minute($minutes = 1) Alias for minutes(). + * @method static static seconds($seconds = 1) Create instance specifying a number of seconds for date interval or replace the interval by the given a number of seconds if called on an instance. + * @method static static second($seconds = 1) Alias for seconds(). + * @method static static milliseconds($milliseconds = 1) Create instance specifying a number of milliseconds for date interval or replace the interval by the given a number of milliseconds if called on an instance. + * @method static static millisecond($milliseconds = 1) Alias for milliseconds(). + * @method static static microseconds($microseconds = 1) Create instance specifying a number of microseconds for date interval or replace the interval by the given a number of microseconds if called on an instance. + * @method static static microsecond($microseconds = 1) Alias for microseconds(). + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundWeek(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundWeeks(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorWeek(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorWeeks(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilWeek(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilWeeks(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CarbonPeriod implements Iterator, Countable, JsonSerializable +{ + use IntervalRounding; + use Mixin { + Mixin::mixin as baseMixin; + } + use Options; + use ToStringFormat; + + /** + * Built-in filter for limit by recurrences. + * + * @var callable + */ + public const RECURRENCES_FILTER = [self::class, 'filterRecurrences']; + + /** + * Built-in filter for limit to an end. + * + * @var callable + */ + public const END_DATE_FILTER = [self::class, 'filterEndDate']; + + /** + * Special value which can be returned by filters to end iteration. Also a filter. + * + * @var callable + */ + public const END_ITERATION = [self::class, 'endIteration']; + + /** + * Exclude start date from iteration. + * + * @var int + */ + public const EXCLUDE_START_DATE = 1; + + /** + * Exclude end date from iteration. + * + * @var int + */ + public const EXCLUDE_END_DATE = 2; + + /** + * Yield CarbonImmutable instances. + * + * @var int + */ + public const IMMUTABLE = 4; + + /** + * Number of maximum attempts before giving up on finding next valid date. + * + * @var int + */ + public const NEXT_MAX_ATTEMPTS = 1000; + + /** + * Number of maximum attempts before giving up on finding end date. + * + * @var int + */ + public const END_MAX_ATTEMPTS = 10000; + + /** + * Default date class of iteration items. + * + * @var string + */ + protected const DEFAULT_DATE_CLASS = Carbon::class; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = []; + + /** + * Date class of iteration items. + * + * @var string + */ + protected $dateClass = Carbon::class; + + /** + * Underlying date interval instance. Always present, one day by default. + * + * @var CarbonInterval + */ + protected $dateInterval; + + /** + * True once __construct is finished. + * + * @var bool + */ + protected $constructed = false; + + /** + * Whether current date interval was set by default. + * + * @var bool + */ + protected $isDefaultInterval; + + /** + * The filters stack. + * + * @var array + */ + protected $filters = []; + + /** + * Period start date. Applied on rewind. Always present, now by default. + * + * @var CarbonInterface + */ + protected $startDate; + + /** + * Period end date. For inverted interval should be before the start date. Applied via a filter. + * + * @var CarbonInterface|null + */ + protected $endDate; + + /** + * Limit for number of recurrences. Applied via a filter. + * + * @var int|null + */ + protected $recurrences; + + /** + * Iteration options. + * + * @var int + */ + protected $options; + + /** + * Index of current date. Always sequential, even if some dates are skipped by filters. + * Equal to null only before the first iteration. + * + * @var int + */ + protected $key; + + /** + * Current date. May temporarily hold unaccepted value when looking for a next valid date. + * Equal to null only before the first iteration. + * + * @var CarbonInterface + */ + protected $current; + + /** + * Timezone of current date. Taken from the start date. + * + * @var \DateTimeZone|null + */ + protected $timezone; + + /** + * The cached validation result for current date. + * + * @var bool|string|null + */ + protected $validationResult; + + /** + * Timezone handler for settings() method. + * + * @var mixed + */ + protected $tzName; + + /** + * Make a CarbonPeriod instance from given variable if possible. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + try { + return static::instance($var); + } catch (NotAPeriodException $e) { + return static::create($var); + } + } + + /** + * Create a new instance from a DatePeriod or CarbonPeriod object. + * + * @param CarbonPeriod|DatePeriod $period + * + * @return static + */ + public static function instance($period) + { + if ($period instanceof static) { + return $period->copy(); + } + + if ($period instanceof self) { + return new static( + $period->getStartDate(), + $period->getEndDate() ?: $period->getRecurrences(), + $period->getDateInterval(), + $period->getOptions() + ); + } + + if ($period instanceof DatePeriod) { + return new static( + $period->start, + $period->end ?: ($period->recurrences - 1), + $period->interval, + $period->include_start_date ? 0 : static::EXCLUDE_START_DATE + ); + } + + $class = static::class; + $type = \gettype($period); + + throw new NotAPeriodException( + 'Argument 1 passed to '.$class.'::'.__METHOD__.'() '. + 'must be an instance of DatePeriod or '.$class.', '. + ($type === 'object' ? 'instance of '.\get_class($period) : $type).' given.' + ); + } + + /** + * Create a new instance. + * + * @return static + */ + public static function create(...$params) + { + return static::createFromArray($params); + } + + /** + * Create a new instance from an array of parameters. + * + * @param array $params + * + * @return static + */ + public static function createFromArray(array $params) + { + return new static(...$params); + } + + /** + * Create CarbonPeriod from ISO 8601 string. + * + * @param string $iso + * @param int|null $options + * + * @return static + */ + public static function createFromIso($iso, $options = null) + { + $params = static::parseIso8601($iso); + + $instance = static::createFromArray($params); + + if ($options !== null) { + $instance->setOptions($options); + } + + return $instance; + } + + /** + * Return whether given interval contains non zero value of any time unit. + * + * @param \DateInterval $interval + * + * @return bool + */ + protected static function intervalHasTime(DateInterval $interval) + { + return $interval->h || $interval->i || $interval->s || $interval->f; + } + + /** + * Return whether given variable is an ISO 8601 specification. + * + * Note: Check is very basic, as actual validation will be done later when parsing. + * We just want to ensure that variable is not any other type of a valid parameter. + * + * @param mixed $var + * + * @return bool + */ + protected static function isIso8601($var) + { + if (!\is_string($var)) { + return false; + } + + // Match slash but not within a timezone name. + $part = '[a-z]+(?:[_-][a-z]+)*'; + + preg_match("#\b$part/$part\b|(/)#i", $var, $match); + + return isset($match[1]); + } + + /** + * Parse given ISO 8601 string into an array of arguments. + * + * @SuppressWarnings(PHPMD.ElseExpression) + * + * @param string $iso + * + * @return array + */ + protected static function parseIso8601($iso) + { + $result = []; + + $interval = null; + $start = null; + $end = null; + $dateClass = static::DEFAULT_DATE_CLASS; + + foreach (explode('/', $iso) as $key => $part) { + if ($key === 0 && preg_match('/^R(\d*|INF)$/', $part, $match)) { + $parsed = \strlen($match[1]) ? (($match[1] !== 'INF') ? (int) $match[1] : INF) : null; + } elseif ($interval === null && $parsed = CarbonInterval::make($part)) { + $interval = $part; + } elseif ($start === null && $parsed = $dateClass::make($part)) { + $start = $part; + } elseif ($end === null && $parsed = $dateClass::make(static::addMissingParts($start ?? '', $part))) { + $end = $part; + } else { + throw new InvalidPeriodParameterException("Invalid ISO 8601 specification: $iso."); + } + + $result[] = $parsed; + } + + return $result; + } + + /** + * Add missing parts of the target date from the source date. + * + * @param string $source + * @param string $target + * + * @return string + */ + protected static function addMissingParts($source, $target) + { + $pattern = '/'.preg_replace('/\d+/', '[0-9]+', preg_quote($target, '/')).'$/'; + + $result = preg_replace($pattern, $target, $source, 1, $count); + + return $count ? $result : $target; + } + + /** + * Register a custom macro. + * + * @example + * ``` + * CarbonPeriod::macro('middle', function () { + * return $this->getStartDate()->average($this->getEndDate()); + * }); + * echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Register macros from a mixin object. + * + * @example + * ``` + * CarbonPeriod::mixin(new class { + * public function addDays() { + * return function ($count = 1) { + * return $this->setStartDate( + * $this->getStartDate()->addDays($count) + * )->setEndDate( + * $this->getEndDate()->addDays($count) + * ); + * }; + * } + * public function subDays() { + * return function ($count = 1) { + * return $this->setStartDate( + * $this->getStartDate()->subDays($count) + * )->setEndDate( + * $this->getEndDate()->subDays($count) + * ); + * }; + * } + * }); + * echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3); + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + static::baseMixin($mixin); + } + + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Provide static proxy for instance aliases. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + $date = new static(); + + if (static::hasMacro($method)) { + return static::bindMacroContext(null, function () use (&$method, &$parameters, &$date) { + return $date->callMacro($method, $parameters); + }); + } + + return $date->$method(...$parameters); + } + + /** + * CarbonPeriod constructor. + * + * @SuppressWarnings(PHPMD.ElseExpression) + * + * @throws InvalidArgumentException + */ + public function __construct(...$arguments) + { + if (is_a($this->dateClass, DateTimeImmutable::class, true)) { + $this->options = static::IMMUTABLE; + } + + // Parse and assign arguments one by one. First argument may be an ISO 8601 spec, + // which will be first parsed into parts and then processed the same way. + + $argumentsCount = \count($arguments); + + if ($argumentsCount && static::isIso8601($iso = $arguments[0])) { + array_splice($arguments, 0, 1, static::parseIso8601($iso)); + } + + if ($argumentsCount === 1) { + if ($arguments[0] instanceof DatePeriod) { + $arguments = [ + $arguments[0]->start, + $arguments[0]->end ?: ($arguments[0]->recurrences - 1), + $arguments[0]->interval, + $arguments[0]->include_start_date ? 0 : static::EXCLUDE_START_DATE, + ]; + } elseif ($arguments[0] instanceof self) { + $arguments = [ + $arguments[0]->getStartDate(), + $arguments[0]->getEndDate() ?: $arguments[0]->getRecurrences(), + $arguments[0]->getDateInterval(), + $arguments[0]->getOptions(), + ]; + } + } + + $optionsSet = false; + + foreach ($arguments as $argument) { + $parsedDate = null; + + if ($argument instanceof DateTimeZone) { + $this->setTimezone($argument); + } elseif ($this->dateInterval === null && + ( + (\is_string($argument) && preg_match( + '/^(-?\d(\d(?![\/-])|[^\d\/-]([\/-])?)*|P[T\d].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i', + $argument + )) || + $argument instanceof DateInterval || + $argument instanceof Closure + ) && + $parsedInterval = @CarbonInterval::make($argument) + ) { + $this->setDateInterval($parsedInterval); + } elseif ($this->startDate === null && $parsedDate = $this->makeDateTime($argument)) { + $this->setStartDate($parsedDate); + } elseif ($this->endDate === null && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) { + $this->setEndDate($parsedDate); + } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) { + $this->setRecurrences($argument); + } elseif (!$optionsSet && (\is_int($argument) || $argument === null)) { + $optionsSet = true; + $this->setOptions(((int) $this->options) | ((int) $argument)); + } else { + throw new InvalidPeriodParameterException('Invalid constructor parameters.'); + } + } + + if ($this->startDate === null) { + $dateClass = $this->dateClass; + $this->setStartDate($dateClass::now()); + } + + if ($this->dateInterval === null) { + $this->setDateInterval(CarbonInterval::day()); + + $this->isDefaultInterval = true; + } + + if ($this->options === null) { + $this->setOptions(0); + } + + $this->constructed = true; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * Prepare the instance to be set (self if mutable to be mutated, + * copy if immutable to generate a new instance). + * + * @return static + */ + protected function copyIfImmutable() + { + return $this; + } + + /** + * Get the getter for a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return callable|null + */ + protected function getGetter(string $name) + { + switch (strtolower(preg_replace('/[A-Z]/', '_$0', $name))) { + case 'start': + case 'start_date': + return [$this, 'getStartDate']; + case 'end': + case 'end_date': + return [$this, 'getEndDate']; + case 'interval': + case 'date_interval': + return [$this, 'getDateInterval']; + case 'recurrences': + return [$this, 'getRecurrences']; + case 'include_start_date': + return [$this, 'isStartIncluded']; + case 'include_end_date': + return [$this, 'isEndIncluded']; + case 'current': + return [$this, 'current']; + default: + return null; + } + } + + /** + * Get a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return bool|CarbonInterface|CarbonInterval|int|null + */ + public function get(string $name) + { + $getter = $this->getGetter($name); + + if ($getter) { + return $getter(); + } + + throw new UnknownGetterException($name); + } + + /** + * Get a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return bool|CarbonInterface|CarbonInterval|int|null + */ + public function __get(string $name) + { + return $this->get($name); + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset(string $name): bool + { + return $this->getGetter($name) !== null; + } + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Set the iteration item class. + * + * @param string $dateClass + * + * @return static + */ + public function setDateClass(string $dateClass) + { + if (!is_a($dateClass, CarbonInterface::class, true)) { + throw new NotACarbonClassException($dateClass); + } + + $self = $this->copyIfImmutable(); + $self->dateClass = $dateClass; + + if (is_a($dateClass, Carbon::class, true)) { + $self->options = $self->options & ~static::IMMUTABLE; + } elseif (is_a($dateClass, CarbonImmutable::class, true)) { + $self->options = $self->options | static::IMMUTABLE; + } + + return $self; + } + + /** + * Returns iteration item date class. + * + * @return string + */ + public function getDateClass(): string + { + return $this->dateClass; + } + + /** + * Change the period date interval. + * + * @param DateInterval|string $interval + * + * @throws InvalidIntervalException + * + * @return static + */ + public function setDateInterval($interval) + { + if (!$interval = CarbonInterval::make($interval)) { + throw new InvalidIntervalException('Invalid interval.'); + } + + if ($interval->spec() === 'PT0S' && !$interval->f && !$interval->getStep()) { + throw new InvalidIntervalException('Empty interval is not accepted.'); + } + + $self = $this->copyIfImmutable(); + $self->dateInterval = $interval; + + $self->isDefaultInterval = false; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Invert the period date interval. + * + * @return static + */ + public function invertDateInterval() + { + return $this->setDateInterval($this->dateInterval->invert()); + } + + /** + * Set start and end date. + * + * @param DateTime|DateTimeInterface|string $start + * @param DateTime|DateTimeInterface|string|null $end + * + * @return static + */ + public function setDates($start, $end) + { + return $this->setStartDate($start)->setEndDate($end); + } + + /** + * Change the period options. + * + * @param int|null $options + * + * @throws InvalidArgumentException + * + * @return static + */ + public function setOptions($options) + { + if (!\is_int($options) && $options !== null) { + throw new InvalidPeriodParameterException('Invalid options.'); + } + + $self = $this->copyIfImmutable(); + $self->options = $options ?: 0; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Get the period options. + * + * @return int + */ + public function getOptions() + { + return $this->options; + } + + /** + * Toggle given options on or off. + * + * @param int $options + * @param bool|null $state + * + * @throws \InvalidArgumentException + * + * @return static + */ + public function toggleOptions($options, $state = null) + { + if ($state === null) { + $state = ($this->options & $options) !== $options; + } + + return $this->setOptions( + $state ? + $this->options | $options : + $this->options & ~$options + ); + } + + /** + * Toggle EXCLUDE_START_DATE option. + * + * @param bool $state + * + * @return static + */ + public function excludeStartDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_START_DATE, $state); + } + + /** + * Toggle EXCLUDE_END_DATE option. + * + * @param bool $state + * + * @return static + */ + public function excludeEndDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_END_DATE, $state); + } + + /** + * Get the underlying date interval. + * + * @return CarbonInterval + */ + public function getDateInterval() + { + return $this->dateInterval->copy(); + } + + /** + * Get start date of the period. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface + */ + public function getStartDate(string $rounding = null) + { + $date = $this->startDate->avoidMutation(); + + return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date; + } + + /** + * Get end date of the period. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface|null + */ + public function getEndDate(string $rounding = null) + { + if (!$this->endDate) { + return null; + } + + $date = $this->endDate->avoidMutation(); + + return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date; + } + + /** + * Get number of recurrences. + * + * @return int|float|null + */ + public function getRecurrences() + { + return $this->recurrences; + } + + /** + * Returns true if the start date should be excluded. + * + * @return bool + */ + public function isStartExcluded() + { + return ($this->options & static::EXCLUDE_START_DATE) !== 0; + } + + /** + * Returns true if the end date should be excluded. + * + * @return bool + */ + public function isEndExcluded() + { + return ($this->options & static::EXCLUDE_END_DATE) !== 0; + } + + /** + * Returns true if the start date should be included. + * + * @return bool + */ + public function isStartIncluded() + { + return !$this->isStartExcluded(); + } + + /** + * Returns true if the end date should be included. + * + * @return bool + */ + public function isEndIncluded() + { + return !$this->isEndExcluded(); + } + + /** + * Return the start if it's included by option, else return the start + 1 period interval. + * + * @return CarbonInterface + */ + public function getIncludedStartDate() + { + $start = $this->getStartDate(); + + if ($this->isStartExcluded()) { + return $start->add($this->getDateInterval()); + } + + return $start; + } + + /** + * Return the end if it's included by option, else return the end - 1 period interval. + * Warning: if the period has no fixed end, this method will iterate the period to calculate it. + * + * @return CarbonInterface + */ + public function getIncludedEndDate() + { + $end = $this->getEndDate(); + + if (!$end) { + return $this->calculateEnd(); + } + + if ($this->isEndExcluded()) { + return $end->sub($this->getDateInterval()); + } + + return $end; + } + + /** + * Add a filter to the stack. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param callable $callback + * @param string $name + * + * @return static + */ + public function addFilter($callback, $name = null) + { + $self = $this->copyIfImmutable(); + $tuple = $self->createFilterTuple(\func_get_args()); + + $self->filters[] = $tuple; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Prepend a filter to the stack. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param callable $callback + * @param string $name + * + * @return static + */ + public function prependFilter($callback, $name = null) + { + $self = $this->copyIfImmutable(); + $tuple = $self->createFilterTuple(\func_get_args()); + + array_unshift($self->filters, $tuple); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Remove a filter by instance or name. + * + * @param callable|string $filter + * + * @return static + */ + public function removeFilter($filter) + { + $self = $this->copyIfImmutable(); + $key = \is_callable($filter) ? 0 : 1; + + $self->filters = array_values(array_filter( + $this->filters, + function ($tuple) use ($key, $filter) { + return $tuple[$key] !== $filter; + } + )); + + $self->updateInternalState(); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Return whether given instance or name is in the filter stack. + * + * @param callable|string $filter + * + * @return bool + */ + public function hasFilter($filter) + { + $key = \is_callable($filter) ? 0 : 1; + + foreach ($this->filters as $tuple) { + if ($tuple[$key] === $filter) { + return true; + } + } + + return false; + } + + /** + * Get filters stack. + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Set filters stack. + * + * @param array $filters + * + * @return static + */ + public function setFilters(array $filters) + { + $self = $this->copyIfImmutable(); + $self->filters = $filters; + + $self->updateInternalState(); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Reset filters stack. + * + * @return static + */ + public function resetFilters() + { + $self = $this->copyIfImmutable(); + $self->filters = []; + + if ($self->endDate !== null) { + $self->filters[] = [static::END_DATE_FILTER, null]; + } + + if ($self->recurrences !== null) { + $self->filters[] = [static::RECURRENCES_FILTER, null]; + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Add a recurrences filter (set maximum number of recurrences). + * + * @param int|float|null $recurrences + * + * @throws InvalidArgumentException + * + * @return static + */ + public function setRecurrences($recurrences) + { + if ((!is_numeric($recurrences) && $recurrences !== null) || $recurrences < 0) { + throw new InvalidPeriodParameterException('Invalid number of recurrences.'); + } + + if ($recurrences === null) { + return $this->removeFilter(static::RECURRENCES_FILTER); + } + + /** @var self $self */ + $self = $this->copyIfImmutable(); + $self->recurrences = $recurrences === INF ? INF : (int) $recurrences; + + if (!$self->hasFilter(static::RECURRENCES_FILTER)) { + return $self->addFilter(static::RECURRENCES_FILTER); + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Change the period start date. + * + * @param DateTime|DateTimeInterface|string $date + * @param bool|null $inclusive + * + * @throws InvalidPeriodDateException + * + * @return static + */ + public function setStartDate($date, $inclusive = null) + { + if (!$this->isInfiniteDate($date) && !($date = ([$this->dateClass, 'make'])($date))) { + throw new InvalidPeriodDateException('Invalid start date.'); + } + + $self = $this->copyIfImmutable(); + $self->startDate = $date; + + if ($inclusive !== null) { + $self = $self->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive); + } + + return $self; + } + + /** + * Change the period end date. + * + * @param DateTime|DateTimeInterface|string|null $date + * @param bool|null $inclusive + * + * @throws \InvalidArgumentException + * + * @return static + */ + public function setEndDate($date, $inclusive = null) + { + if ($date !== null && !$this->isInfiniteDate($date) && !$date = ([$this->dateClass, 'make'])($date)) { + throw new InvalidPeriodDateException('Invalid end date.'); + } + + if (!$date) { + return $this->removeFilter(static::END_DATE_FILTER); + } + + $self = $this->copyIfImmutable(); + $self->endDate = $date; + + if ($inclusive !== null) { + $self = $self->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive); + } + + if (!$self->hasFilter(static::END_DATE_FILTER)) { + return $self->addFilter(static::END_DATE_FILTER); + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Check if the current position is valid. + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + return $this->validateCurrentDate() === true; + } + + /** + * Return the current key. + * + * @return int|null + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->valid() + ? $this->key + : null; + } + + /** + * Return the current date. + * + * @return CarbonInterface|null + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->valid() + ? $this->prepareForReturn($this->current) + : null; + } + + /** + * Move forward to the next date. + * + * @throws RuntimeException + * + * @return void + */ + #[ReturnTypeWillChange] + public function next() + { + if ($this->current === null) { + $this->rewind(); + } + + if ($this->validationResult !== static::END_ITERATION) { + $this->key++; + + $this->incrementCurrentDateUntilValid(); + } + } + + /** + * Rewind to the start date. + * + * Iterating over a date in the UTC timezone avoids bug during backward DST change. + * + * @see https://bugs.php.net/bug.php?id=72255 + * @see https://bugs.php.net/bug.php?id=74274 + * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time + * + * @throws RuntimeException + * + * @return void + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->key = 0; + $this->current = ([$this->dateClass, 'make'])($this->startDate); + $settings = $this->getSettings(); + + if ($this->hasLocalTranslator()) { + $settings['locale'] = $this->getTranslatorLocale(); + } + + $this->current->settings($settings); + $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null; + + if ($this->timezone) { + $this->current = $this->current->utc(); + } + + $this->validationResult = null; + + if ($this->isStartExcluded() || $this->validateCurrentDate() === false) { + $this->incrementCurrentDateUntilValid(); + } + } + + /** + * Skip iterations and returns iteration state (false if ended, true if still valid). + * + * @param int $count steps number to skip (1 by default) + * + * @return bool + */ + public function skip($count = 1) + { + for ($i = $count; $this->valid() && $i > 0; $i--) { + $this->next(); + } + + return $this->valid(); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function toIso8601String() + { + $parts = []; + + if ($this->recurrences !== null) { + $parts[] = 'R'.$this->recurrences; + } + + $parts[] = $this->startDate->toIso8601String(); + + $parts[] = $this->dateInterval->spec(); + + if ($this->endDate !== null) { + $parts[] = $this->endDate->toIso8601String(); + } + + return implode('/', $parts); + } + + /** + * Convert the date period into a string. + * + * @return string + */ + public function toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + if ($format instanceof Closure) { + return $format($this); + } + + $translator = ([$this->dateClass, 'getTranslator'])(); + + $parts = []; + + $format = $format ?? ( + !$this->startDate->isStartOfDay() || ($this->endDate && !$this->endDate->isStartOfDay()) + ? 'Y-m-d H:i:s' + : 'Y-m-d' + ); + + if ($this->recurrences !== null) { + $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator); + } + + $parts[] = $this->translate('period_interval', [':interval' => $this->dateInterval->forHumans([ + 'join' => true, + ])], null, $translator); + + $parts[] = $this->translate('period_start_date', [':date' => $this->startDate->rawFormat($format)], null, $translator); + + if ($this->endDate !== null) { + $parts[] = $this->translate('period_end_date', [':date' => $this->endDate->rawFormat($format)], null, $translator); + } + + $result = implode(' ', $parts); + + return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function spec() + { + return $this->toIso8601String(); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DatePeriod + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DatePeriod::class, true)) { + return new $className( + $this->rawDate($this->getStartDate()), + $this->getDateInterval(), + $this->getEndDate() ? $this->rawDate($this->getIncludedEndDate()) : $this->getRecurrences(), + $this->isStartExcluded() ? DatePeriod::EXCLUDE_START_DATE : 0 + ); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } + + /** + * Return native DatePeriod PHP object matching the current instance. + * + * @example + * ``` + * var_dump(CarbonPeriod::create('2021-01-05', '2021-02-15')->toDatePeriod()); + * ``` + * + * @return DatePeriod + */ + public function toDatePeriod() + { + return $this->cast(DatePeriod::class); + } + + /** + * Return `true` if the period has no custom filter and is guaranteed to be endless. + * + * Note that we can't check if a period is endless as soon as it has custom filters + * because filters can emit `CarbonPeriod::END_ITERATION` to stop the iteration in + * a way we can't predict without actually iterating the period. + */ + public function isUnfilteredAndEndLess(): bool + { + foreach ($this->filters as $filter) { + switch ($filter) { + case [static::RECURRENCES_FILTER, null]: + if ($this->recurrences !== null && is_finite($this->recurrences)) { + return false; + } + + break; + + case [static::END_DATE_FILTER, null]: + if ($this->endDate !== null && !$this->endDate->isEndOfTime()) { + return false; + } + + break; + + default: + return false; + } + } + + return true; + } + + /** + * Convert the date period into an array without changing current iteration state. + * + * @return CarbonInterface[] + */ + public function toArray() + { + if ($this->isUnfilteredAndEndLess()) { + throw new EndLessPeriodException("Endless period can't be converted to array nor counted."); + } + + $state = [ + $this->key, + $this->current ? $this->current->avoidMutation() : null, + $this->validationResult, + ]; + + $result = iterator_to_array($this); + + [$this->key, $this->current, $this->validationResult] = $state; + + return $result; + } + + /** + * Count dates in the date period. + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return \count($this->toArray()); + } + + /** + * Return the first date in the date period. + * + * @return CarbonInterface|null + */ + public function first() + { + if ($this->isUnfilteredAndEndLess()) { + foreach ($this as $date) { + $this->rewind(); + + return $date; + } + + return null; + } + + return ($this->toArray() ?: [])[0] ?? null; + } + + /** + * Return the last date in the date period. + * + * @return CarbonInterface|null + */ + public function last() + { + $array = $this->toArray(); + + return $array ? $array[\count($array) - 1] : null; + } + + /** + * Convert the date period into a string. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Add aliases for setters. + * + * CarbonPeriod::days(3)->hours(5)->invert() + * ->sinceNow()->until('2010-01-10') + * ->filter(...) + * ->count() + * + * Note: We use magic method to let static and instance aliases with the same names. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + return $this->callMacro($method, $parameters); + }); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + switch ($method) { + case 'start': + case 'since': + self::setDefaultParameters($parameters, [ + [0, 'date', null], + ]); + + return $this->setStartDate(...$parameters); + + case 'sinceNow': + return $this->setStartDate(new Carbon(), ...$parameters); + + case 'end': + case 'until': + self::setDefaultParameters($parameters, [ + [0, 'date', null], + ]); + + return $this->setEndDate(...$parameters); + + case 'untilNow': + return $this->setEndDate(new Carbon(), ...$parameters); + + case 'dates': + case 'between': + self::setDefaultParameters($parameters, [ + [0, 'start', null], + [1, 'end', null], + ]); + + return $this->setDates(...$parameters); + + case 'recurrences': + case 'times': + self::setDefaultParameters($parameters, [ + [0, 'recurrences', null], + ]); + + return $this->setRecurrences(...$parameters); + + case 'options': + self::setDefaultParameters($parameters, [ + [0, 'options', null], + ]); + + return $this->setOptions(...$parameters); + + case 'toggle': + self::setDefaultParameters($parameters, [ + [0, 'options', null], + ]); + + return $this->toggleOptions(...$parameters); + + case 'filter': + case 'push': + return $this->addFilter(...$parameters); + + case 'prepend': + return $this->prependFilter(...$parameters); + + case 'filters': + self::setDefaultParameters($parameters, [ + [0, 'filters', []], + ]); + + return $this->setFilters(...$parameters); + + case 'interval': + case 'each': + case 'every': + case 'step': + case 'stepBy': + return $this->setDateInterval(...$parameters); + + case 'invert': + return $this->invertDateInterval(); + + case 'years': + case 'year': + case 'months': + case 'month': + case 'weeks': + case 'week': + case 'days': + case 'dayz': + case 'day': + case 'hours': + case 'hour': + case 'minutes': + case 'minute': + case 'seconds': + case 'second': + case 'milliseconds': + case 'millisecond': + case 'microseconds': + case 'microsecond': + return $this->setDateInterval(( + // Override default P1D when instantiating via fluent setters. + [$this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method] + )(...$parameters)); + } + + $dateClass = $this->dateClass; + + if ($this->localStrictModeEnabled ?? $dateClass::isStrictModeEnabled()) { + throw new UnknownMethodException($method); + } + + return $this; + } + + /** + * Set the instance's timezone from a string or object and apply it to start/end. + * + * @param \DateTimeZone|string $timezone + * + * @return static + */ + public function setTimezone($timezone) + { + $self = $this->copyIfImmutable(); + $self->tzName = $timezone; + $self->timezone = $timezone; + + if ($self->startDate) { + $self = $self->setStartDate($self->startDate->setTimezone($timezone)); + } + + if ($self->endDate) { + $self = $self->setEndDate($self->endDate->setTimezone($timezone)); + } + + return $self; + } + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference to start/end. + * + * @param \DateTimeZone|string $timezone + * + * @return static + */ + public function shiftTimezone($timezone) + { + $self = $this->copyIfImmutable(); + $self->tzName = $timezone; + $self->timezone = $timezone; + + if ($self->startDate) { + $self = $self->setStartDate($self->startDate->shiftTimezone($timezone)); + } + + if ($self->endDate) { + $self = $self->setEndDate($self->endDate->shiftTimezone($timezone)); + } + + return $self; + } + + /** + * Returns the end is set, else calculated from start an recurrences. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface + */ + public function calculateEnd(string $rounding = null) + { + if ($end = $this->getEndDate($rounding)) { + return $end; + } + + if ($this->dateInterval->isEmpty()) { + return $this->getStartDate($rounding); + } + + $date = $this->getEndFromRecurrences() ?? $this->iterateUntilEnd(); + + if ($date && $rounding) { + $date = $date->avoidMutation()->round($this->getDateInterval(), $rounding); + } + + return $date; + } + + /** + * @return CarbonInterface|null + */ + private function getEndFromRecurrences() + { + if ($this->recurrences === null) { + throw new UnreachableException( + "Could not calculate period end without either explicit end or recurrences.\n". + "If you're looking for a forever-period, use ->setRecurrences(INF)." + ); + } + + if ($this->recurrences === INF) { + $start = $this->getStartDate(); + + return $start < $start->avoidMutation()->add($this->getDateInterval()) + ? CarbonImmutable::endOfTime() + : CarbonImmutable::startOfTime(); + } + + if ($this->filters === [[static::RECURRENCES_FILTER, null]]) { + return $this->getStartDate()->avoidMutation()->add( + $this->getDateInterval()->times( + $this->recurrences - ($this->isStartExcluded() ? 0 : 1) + ) + ); + } + + return null; + } + + /** + * @return CarbonInterface|null + */ + private function iterateUntilEnd() + { + $attempts = 0; + $date = null; + + foreach ($this as $date) { + if (++$attempts > static::END_MAX_ATTEMPTS) { + throw new UnreachableException( + 'Could not calculate period end after iterating '.static::END_MAX_ATTEMPTS.' times.' + ); + } + } + + return $date; + } + + /** + * Returns true if the current period overlaps the given one (if 1 parameter passed) + * or the period between 2 dates (if 2 parameters passed). + * + * @param CarbonPeriod|\DateTimeInterface|EDD\Vendor\Carbon|CarbonImmutable|string $rangeOrRangeStart + * @param \DateTimeInterface|EDD\Vendor\Carbon|CarbonImmutable|string|null $rangeEnd + * + * @return bool + */ + public function overlaps($rangeOrRangeStart, $rangeEnd = null) + { + $range = $rangeEnd ? static::create($rangeOrRangeStart, $rangeEnd) : $rangeOrRangeStart; + + if (!($range instanceof self)) { + $range = static::create($range); + } + + [$start, $end] = $this->orderCouple($this->getStartDate(), $this->calculateEnd()); + [$rangeStart, $rangeEnd] = $this->orderCouple($range->getStartDate(), $range->calculateEnd()); + + return $end > $rangeStart && $rangeEnd > $start; + } + + /** + * Execute a given function on each date of the period. + * + * @example + * ``` + * Carbon::create('2020-11-29')->daysUntil('2020-12-24')->forEach(function (EDD\Vendor\Carbon $date) { + * echo $date->diffInDays('2020-12-25')." days before Christmas!\n"; + * }); + * ``` + * + * @param callable $callback + */ + public function forEach(callable $callback) + { + foreach ($this as $date) { + $callback($date); + } + } + + /** + * Execute a given function on each date of the period and yield the result of this function. + * + * @example + * ``` + * $period = Carbon::create('2020-11-29')->daysUntil('2020-12-24'); + * echo implode("\n", iterator_to_array($period->map(function (EDD\Vendor\Carbon $date) { + * return $date->diffInDays('2020-12-25').' days before Christmas!'; + * }))); + * ``` + * + * @param callable $callback + * + * @return \Generator + */ + public function map(callable $callback) + { + foreach ($this as $date) { + yield $callback($date); + } + } + + /** + * Determines if the instance is equal to another. + * Warning: if options differ, instances will never be equal. + * + * @param mixed $period + * + * @see equalTo() + * + * @return bool + */ + public function eq($period): bool + { + return $this->equalTo($period); + } + + /** + * Determines if the instance is equal to another. + * Warning: if options differ, instances will never be equal. + * + * @param mixed $period + * + * @return bool + */ + public function equalTo($period): bool + { + if (!($period instanceof self)) { + $period = self::make($period); + } + + $end = $this->getEndDate(); + + return $period !== null + && $this->getDateInterval()->eq($period->getDateInterval()) + && $this->getStartDate()->eq($period->getStartDate()) + && ($end ? $end->eq($period->getEndDate()) : $this->getRecurrences() === $period->getRecurrences()) + && ($this->getOptions() & (~static::IMMUTABLE)) === ($period->getOptions() & (~static::IMMUTABLE)); + } + + /** + * Determines if the instance is not equal to another. + * Warning: if options differ, instances will never be equal. + * + * @param mixed $period + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($period): bool + { + return $this->notEqualTo($period); + } + + /** + * Determines if the instance is not equal to another. + * Warning: if options differ, instances will never be equal. + * + * @param mixed $period + * + * @return bool + */ + public function notEqualTo($period): bool + { + return !$this->eq($period); + } + + /** + * Determines if the start date is before an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsBefore($date = null): bool + { + return $this->getStartDate()->lessThan($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is before or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsBeforeOrAt($date = null): bool + { + return $this->getStartDate()->lessThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is after an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAfter($date = null): bool + { + return $this->getStartDate()->greaterThan($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is after or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAfterOrAt($date = null): bool + { + return $this->getStartDate()->greaterThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAt($date = null): bool + { + return $this->getStartDate()->equalTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is before an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsBefore($date = null): bool + { + return $this->calculateEnd()->lessThan($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is before or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsBeforeOrAt($date = null): bool + { + return $this->calculateEnd()->lessThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is after an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAfter($date = null): bool + { + return $this->calculateEnd()->greaterThan($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is after or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAfterOrAt($date = null): bool + { + return $this->calculateEnd()->greaterThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAt($date = null): bool + { + return $this->calculateEnd()->equalTo($this->resolveCarbon($date)); + } + + /** + * Return true if start date is now or later. + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isStarted(): bool + { + return $this->startsBeforeOrAt(); + } + + /** + * Return true if end date is now or later. + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isEnded(): bool + { + return $this->endsBeforeOrAt(); + } + + /** + * Return true if now is between start date (included) and end date (excluded). + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isInProgress(): bool + { + return $this->isStarted() && !$this->isEnded(); + } + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return static + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $self = $this->copyIfImmutable(); + $self = $self->setStartDate($self->getStartDate()->roundUnit($unit, $precision, $function)); + + if ($self->endDate) { + $self = $self->setEndDate($self->getEndDate()->roundUnit($unit, $precision, $function)); + } + + return $self->setDateInterval($self->getDateInterval()->roundUnit($unit, $precision, $function)); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return static + */ + public function round($precision = null, $function = 'round') + { + return $this->roundWith( + $precision ?? $this->getDateInterval()->setLocalTranslator(TranslatorImmutable::get('en'))->forHumans(), + $function + ); + } + + /** + * Round the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function floor($precision = null) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function ceil($precision = null) + { + return $this->round($precision, 'ceil'); + } + + /** + * Specify data which should be serialized to JSON. + * + * @link https://php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return CarbonInterface[] + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Return true if the given date is between start and end. + * + * @param \EDD\Vendor\Carbon\Carbon|\EDD\Vendor\Carbon\CarbonPeriod|\EDD\Vendor\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * + * @return bool + */ + public function contains($date = null): bool + { + $startMethod = 'startsBefore'.($this->isStartIncluded() ? 'OrAt' : ''); + $endMethod = 'endsAfter'.($this->isEndIncluded() ? 'OrAt' : ''); + + return $this->$startMethod($date) && $this->$endMethod($date); + } + + /** + * Return true if the current period follows a given other period (with no overlap). + * For instance, [2019-08-01 -> 2019-08-12] follows [2019-07-29 -> 2019-07-31] + * Note than in this example, follows() would be false if 2019-08-01 or 2019-07-31 was excluded by options. + * + * @param \EDD\Vendor\Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function follows($period, ...$arguments): bool + { + $period = $this->resolveCarbonPeriod($period, ...$arguments); + + return $this->getIncludedStartDate()->equalTo($period->getIncludedEndDate()->add($period->getDateInterval())); + } + + /** + * Return true if the given other period follows the current one (with no overlap). + * For instance, [2019-07-29 -> 2019-07-31] is followed by [2019-08-01 -> 2019-08-12] + * Note than in this example, isFollowedBy() would be false if 2019-08-01 or 2019-07-31 was excluded by options. + * + * @param \EDD\Vendor\Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function isFollowedBy($period, ...$arguments): bool + { + $period = $this->resolveCarbonPeriod($period, ...$arguments); + + return $period->follows($this); + } + + /** + * Return true if the given period either follows or is followed by the current one. + * + * @see follows() + * @see isFollowedBy() + * + * @param \EDD\Vendor\Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function isConsecutiveWith($period, ...$arguments): bool + { + return $this->follows($period, ...$arguments) || $this->isFollowedBy($period, ...$arguments); + } + + /** + * Update properties after removing built-in filters. + * + * @return void + */ + protected function updateInternalState() + { + if (!$this->hasFilter(static::END_DATE_FILTER)) { + $this->endDate = null; + } + + if (!$this->hasFilter(static::RECURRENCES_FILTER)) { + $this->recurrences = null; + } + } + + /** + * Create a filter tuple from raw parameters. + * + * Will create an automatic filter callback for one of Carbon's is* methods. + * + * @param array $parameters + * + * @return array + */ + protected function createFilterTuple(array $parameters) + { + $method = array_shift($parameters); + + if (!$this->isCarbonPredicateMethod($method)) { + return [$method, array_shift($parameters)]; + } + + return [function ($date) use ($method, $parameters) { + return ([$date, $method])(...$parameters); + }, $method]; + } + + /** + * Return whether given callable is a string pointing to one of Carbon's is* methods + * and should be automatically converted to a filter callback. + * + * @param callable $callable + * + * @return bool + */ + protected function isCarbonPredicateMethod($callable) + { + return \is_string($callable) && str_starts_with($callable, 'is') && + (method_exists($this->dateClass, $callable) || ([$this->dateClass, 'hasMacro'])($callable)); + } + + /** + * Recurrences filter callback (limits number of recurrences). + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param \EDD\Vendor\Carbon\Carbon $current + * @param int $key + * + * @return bool|string + */ + protected function filterRecurrences($current, $key) + { + if ($key < $this->recurrences) { + return true; + } + + return static::END_ITERATION; + } + + /** + * End date filter callback. + * + * @param \EDD\Vendor\Carbon\Carbon $current + * + * @return bool|string + */ + protected function filterEndDate($current) + { + if (!$this->isEndExcluded() && $current == $this->endDate) { + return true; + } + + if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) { + return true; + } + + return static::END_ITERATION; + } + + /** + * End iteration filter callback. + * + * @return string + */ + protected function endIteration() + { + return static::END_ITERATION; + } + + /** + * Handle change of the parameters. + */ + protected function handleChangedParameters() + { + if (($this->getOptions() & static::IMMUTABLE) && $this->dateClass === Carbon::class) { + $this->dateClass = CarbonImmutable::class; + } elseif (!($this->getOptions() & static::IMMUTABLE) && $this->dateClass === CarbonImmutable::class) { + $this->dateClass = Carbon::class; + } + + $this->validationResult = null; + } + + /** + * Validate current date and stop iteration when necessary. + * + * Returns true when current date is valid, false if it is not, or static::END_ITERATION + * when iteration should be stopped. + * + * @return bool|string + */ + protected function validateCurrentDate() + { + if ($this->current === null) { + $this->rewind(); + } + + // Check after the first rewind to avoid repeating the initial validation. + return $this->validationResult ?? ($this->validationResult = $this->checkFilters()); + } + + /** + * Check whether current value and key pass all the filters. + * + * @return bool|string + */ + protected function checkFilters() + { + $current = $this->prepareForReturn($this->current); + + foreach ($this->filters as $tuple) { + $result = \call_user_func( + $tuple[0], + $current->avoidMutation(), + $this->key, + $this + ); + + if ($result === static::END_ITERATION) { + return static::END_ITERATION; + } + + if (!$result) { + return false; + } + } + + return true; + } + + /** + * Prepare given date to be returned to the external logic. + * + * @param CarbonInterface $date + * + * @return CarbonInterface + */ + protected function prepareForReturn(CarbonInterface $date) + { + $date = ([$this->dateClass, 'make'])($date); + + if ($this->timezone) { + $date = $date->setTimezone($this->timezone); + } + + return $date; + } + + /** + * Keep incrementing the current date until a valid date is found or the iteration is ended. + * + * @throws RuntimeException + * + * @return void + */ + protected function incrementCurrentDateUntilValid() + { + $attempts = 0; + + do { + $this->current = $this->current->add($this->dateInterval); + + $this->validationResult = null; + + if (++$attempts > static::NEXT_MAX_ATTEMPTS) { + throw new UnreachableException('Could not find next valid date.'); + } + } while ($this->validateCurrentDate() === false); + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + /** + * Return the EDD\Vendor\Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param \EDD\Vendor\Carbon\Carbon|\EDD\Vendor\Carbon\CarbonPeriod|\EDD\Vendor\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * + * @return \EDD\Vendor\Carbon\CarbonInterface + */ + protected function resolveCarbon($date = null) + { + return $this->getStartDate()->nowWithSameTz()->carbonize($date); + } + + /** + * Resolve passed arguments or DatePeriod to a CarbonPeriod object. + * + * @param mixed $period + * @param mixed ...$arguments + * + * @return static + */ + protected function resolveCarbonPeriod($period, ...$arguments) + { + if ($period instanceof self) { + return $period; + } + + return $period instanceof DatePeriod + ? static::instance($period) + : static::create($period, ...$arguments); + } + + private function orderCouple($first, $second): array + { + return $first > $second ? [$second, $first] : [$first, $second]; + } + + private function makeDateTime($value): ?DateTimeInterface + { + if ($value instanceof DateTimeInterface) { + return $value; + } + + if (\is_string($value)) { + $value = trim($value); + + if (!preg_match('/^P[\dT]/', $value) && + !preg_match('/^R\d/', $value) && + preg_match('/[a-z\d]/i', $value) + ) { + $dateClass = $this->dateClass; + + return $dateClass::parse($value, $this->tzName); + } + } + + return null; + } + + private function isInfiniteDate($date): bool + { + return $date instanceof CarbonInterface && ($date->isEndOfTime() || $date->isStartOfTime()); + } + + private function rawDate($date): ?DateTimeInterface + { + if ($date === false || $date === null) { + return null; + } + + if ($date instanceof CarbonInterface) { + return $date->isMutable() + ? $date->toDateTime() + : $date->toDateTimeImmutable(); + } + + if (\in_array(\get_class($date), [DateTime::class, DateTimeImmutable::class], true)) { + return $date; + } + + $class = $date instanceof DateTime ? DateTime::class : DateTimeImmutable::class; + + return new $class($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + } + + private static function setDefaultParameters(array &$parameters, array $defaults): void + { + foreach ($defaults as [$index, $name, $value]) { + if (!\array_key_exists($index, $parameters) && !\array_key_exists($name, $parameters)) { + $parameters[$index] = $value; + } + } + } +} diff --git a/libraries/Carbon/src/Carbon/CarbonPeriodImmutable.php b/libraries/Carbon/src/Carbon/CarbonPeriodImmutable.php new file mode 100644 index 00000000000..27d9d8790fd --- /dev/null +++ b/libraries/Carbon/src/Carbon/CarbonPeriodImmutable.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +class CarbonPeriodImmutable extends CarbonPeriod +{ + /** + * Default date class of iteration items. + * + * @var string + */ + protected const DEFAULT_DATE_CLASS = CarbonImmutable::class; + + /** + * Date class of iteration items. + * + * @var string + */ + protected $dateClass = CarbonImmutable::class; + + /** + * Prepare the instance to be set (self if mutable to be mutated, + * copy if immutable to generate a new instance). + * + * @return static + */ + protected function copyIfImmutable() + { + return $this->constructed ? clone $this : $this; + } +} diff --git a/libraries/Carbon/src/Carbon/CarbonTimeZone.php b/libraries/Carbon/src/Carbon/CarbonTimeZone.php new file mode 100644 index 00000000000..794cb74a45d --- /dev/null +++ b/libraries/Carbon/src/Carbon/CarbonTimeZone.php @@ -0,0 +1,320 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Carbon\Exceptions\InvalidCastException; +use EDD\Vendor\Carbon\Exceptions\InvalidTimeZoneException; +use DateTimeInterface; +use DateTimeZone; +use Throwable; + +class CarbonTimeZone extends DateTimeZone +{ + public function __construct($timezone = null) + { + parent::__construct(static::getDateTimeZoneNameFromMixed($timezone)); + } + + protected static function parseNumericTimezone($timezone) + { + if ($timezone <= -100 || $timezone >= 100) { + throw new InvalidTimeZoneException('Absolute timezone offset cannot be greater than 100.'); + } + + return ($timezone >= 0 ? '+' : '').ltrim($timezone, '+').':00'; + } + + protected static function getDateTimeZoneNameFromMixed($timezone) + { + if ($timezone === null) { + return date_default_timezone_get(); + } + + if (\is_string($timezone)) { + $timezone = preg_replace('/^\s*([+-]\d+)(\d{2})\s*$/', '$1:$2', $timezone); + } + + if (is_numeric($timezone)) { + return static::parseNumericTimezone($timezone); + } + + return $timezone; + } + + protected static function getDateTimeZoneFromName(&$name) + { + return @timezone_open($name = (string) static::getDateTimeZoneNameFromMixed($name)); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeZone + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DateTimeZone::class, true)) { + return new $className($this->getName()); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } + + /** + * Create a CarbonTimeZone from mixed input. + * + * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * + * @throws InvalidTimeZoneException + * + * @return false|static + */ + public static function instance($object = null, $objectDump = null) + { + $tz = $object; + + if ($tz instanceof static) { + return $tz; + } + + if ($tz === null) { + return new static(); + } + + if (!$tz instanceof DateTimeZone) { + $tz = static::getDateTimeZoneFromName($object); + } + + if ($tz !== false) { + return new static($tz->getName()); + } + + if (Carbon::isStrictModeEnabled()) { + throw new InvalidTimeZoneException('Unknown or bad timezone ('.($objectDump ?: $object).')'); + } + + return false; + } + + /** + * Returns abbreviated name of the current timezone according to DST setting. + * + * @param bool $dst + * + * @return string + */ + public function getAbbreviatedName($dst = false) + { + $name = $this->getName(); + + foreach ($this->listAbbreviations() as $abbreviation => $zones) { + foreach ($zones as $zone) { + if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) { + return $abbreviation; + } + } + } + + return 'unknown'; + } + + /** + * @alias getAbbreviatedName + * + * Returns abbreviated name of the current timezone according to DST setting. + * + * @param bool $dst + * + * @return string + */ + public function getAbbr($dst = false) + { + return $this->getAbbreviatedName($dst); + } + + /** + * Get the offset as string "sHH:MM" (such as "+00:00" or "-12:30"). + * + * @param DateTimeInterface|null $date + * + * @return string + */ + public function toOffsetName(DateTimeInterface $date = null) + { + return static::getOffsetNameFromMinuteOffset( + $this->getOffset($date ?: Carbon::now($this)) / 60 + ); + } + + /** + * Returns a new CarbonTimeZone object using the offset string instead of region string. + * + * @param DateTimeInterface|null $date + * + * @return CarbonTimeZone + */ + public function toOffsetTimeZone(DateTimeInterface $date = null) + { + return new static($this->toOffsetName($date)); + } + + /** + * Returns the first region string (such as "America/Toronto") that matches the current timezone or + * false if no match is found. + * + * @see timezone_name_from_abbr native PHP function. + * + * @param DateTimeInterface|null $date + * @param int $isDst + * + * @return string|false + */ + public function toRegionName(DateTimeInterface $date = null, $isDst = 1) + { + $name = $this->getName(); + $firstChar = substr($name, 0, 1); + + if ($firstChar !== '+' && $firstChar !== '-') { + return $name; + } + + $date = $date ?: Carbon::now($this); + + // Integer construction no longer supported since PHP 8 + // @codeCoverageIgnoreStart + try { + $offset = @$this->getOffset($date) ?: 0; + } catch (Throwable $e) { + $offset = 0; + } + // @codeCoverageIgnoreEnd + + $name = @timezone_name_from_abbr('', $offset, $isDst); + + if ($name) { + return $name; + } + + foreach (timezone_identifiers_list() as $timezone) { + if (Carbon::instance($date)->tz($timezone)->getOffset() === $offset) { + return $timezone; + } + } + + return false; + } + + /** + * Returns a new CarbonTimeZone object using the region string instead of offset string. + * + * @param DateTimeInterface|null $date + * + * @return CarbonTimeZone|false + */ + public function toRegionTimeZone(DateTimeInterface $date = null) + { + $tz = $this->toRegionName($date); + + if ($tz !== false) { + return new static($tz); + } + + if (Carbon::isStrictModeEnabled()) { + throw new InvalidTimeZoneException('Unknown timezone for offset '.$this->getOffset($date ?: Carbon::now($this)).' seconds.'); + } + + return false; + } + + /** + * Cast to string (get timezone name). + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Return the type number: + * + * Type 1; A UTC offset, such as -0300 + * Type 2; A timezone abbreviation, such as GMT + * Type 3: A timezone identifier, such as Europe/London + */ + public function getType(): int + { + return preg_match('/"timezone_type";i:(\d)/', serialize($this), $match) ? (int) $match[1] : 3; + } + + /** + * Create a CarbonTimeZone from mixed input. + * + * @param DateTimeZone|string|int|null $object + * + * @return false|static + */ + public static function create($object = null) + { + return static::instance($object); + } + + /** + * Create a CarbonTimeZone from int/float hour offset. + * + * @param float $hourOffset number of hour of the timezone shift (can be decimal). + * + * @return false|static + */ + public static function createFromHourOffset(float $hourOffset) + { + return static::createFromMinuteOffset($hourOffset * Carbon::MINUTES_PER_HOUR); + } + + /** + * Create a CarbonTimeZone from int/float minute offset. + * + * @param float $minuteOffset number of total minutes of the timezone shift. + * + * @return false|static + */ + public static function createFromMinuteOffset(float $minuteOffset) + { + return static::instance(static::getOffsetNameFromMinuteOffset($minuteOffset)); + } + + /** + * Convert a total minutes offset into a standardized timezone offset string. + * + * @param float $minutes number of total minutes of the timezone shift. + * + * @return string + */ + public static function getOffsetNameFromMinuteOffset(float $minutes): string + { + $minutes = round($minutes); + $unsignedMinutes = abs($minutes); + + return ($minutes < 0 ? '-' : '+'). + str_pad((string) floor($unsignedMinutes / 60), 2, '0', STR_PAD_LEFT). + ':'. + str_pad((string) ($unsignedMinutes % 60), 2, '0', STR_PAD_LEFT); + } +} diff --git a/libraries/Carbon/src/Carbon/Cli/Invoker.php b/libraries/Carbon/src/Carbon/Cli/Invoker.php new file mode 100644 index 00000000000..560610e36e8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Cli/Invoker.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Cli; + +class Invoker +{ + public const CLI_CLASS_NAME = 'EDD\Vendor\Carbon\\Cli'; + + protected function runWithCli(string $className, array $parameters): bool + { + $cli = new $className(); + + return $cli(...$parameters); + } + + public function __invoke(...$parameters): bool + { + if (class_exists(self::CLI_CLASS_NAME)) { + return $this->runWithCli(self::CLI_CLASS_NAME, $parameters); + } + + $function = (($parameters[1] ?? '') === 'install' ? ($parameters[2] ?? null) : null) ?: 'shell_exec'; + $function('composer require carbon-cli/carbon-cli --no-interaction'); + + echo 'Installation succeeded.'; + + return true; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/BadComparisonUnitException.php b/libraries/Carbon/src/Carbon/Exceptions/BadComparisonUnitException.php new file mode 100644 index 00000000000..ad9b8e7df61 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/BadComparisonUnitException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use Throwable; + +class BadComparisonUnitException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Bad comparison unit: '$unit'", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/BadFluentConstructorException.php b/libraries/Carbon/src/Carbon/Exceptions/BadFluentConstructorException.php new file mode 100644 index 00000000000..b788587a3be --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/BadFluentConstructorException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class BadFluentConstructorException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The method. + * + * @var string + */ + protected $method; + + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($method, $code = 0, Throwable $previous = null) + { + $this->method = $method; + + parent::__construct(sprintf("Unknown fluent constructor '%s'.", $method), $code, $previous); + } + + /** + * Get the method. + * + * @return string + */ + public function getMethod(): string + { + return $this->method; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/BadFluentSetterException.php b/libraries/Carbon/src/Carbon/Exceptions/BadFluentSetterException.php new file mode 100644 index 00000000000..31b40566952 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/BadFluentSetterException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class BadFluentSetterException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The setter. + * + * @var string + */ + protected $setter; + + /** + * Constructor. + * + * @param string $setter + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($setter, $code = 0, Throwable $previous = null) + { + $this->setter = $setter; + + parent::__construct(sprintf("Unknown fluent setter '%s'", $setter), $code, $previous); + } + + /** + * Get the setter. + * + * @return string + */ + public function getSetter(): string + { + return $this->setter; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/BadMethodCallException.php b/libraries/Carbon/src/Carbon/Exceptions/BadMethodCallException.php new file mode 100644 index 00000000000..f1e71c1abf2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/BadMethodCallException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +interface BadMethodCallException extends Exception +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/EndLessPeriodException.php b/libraries/Carbon/src/Carbon/Exceptions/EndLessPeriodException.php new file mode 100644 index 00000000000..bd95db92a6f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/EndLessPeriodException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; + +final class EndLessPeriodException extends BaseRuntimeException implements RuntimeException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/Exception.php b/libraries/Carbon/src/Carbon/Exceptions/Exception.php new file mode 100644 index 00000000000..6aa14683b55 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/Exception.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +interface Exception +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/ImmutableException.php b/libraries/Carbon/src/Carbon/Exceptions/ImmutableException.php new file mode 100644 index 00000000000..212c4ec1b3f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/ImmutableException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; +use Throwable; + +class ImmutableException extends BaseRuntimeException implements RuntimeException +{ + /** + * The value. + * + * @var string + */ + protected $value; + + /** + * Constructor. + * + * @param string $value the immutable type/value + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($value, $code = 0, Throwable $previous = null) + { + $this->value = $value; + parent::__construct("$value is immutable.", $code, $previous); + } + + /** + * Get the value. + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidArgumentException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidArgumentException.php new file mode 100644 index 00000000000..6f0c1715287 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidArgumentException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +interface InvalidArgumentException extends Exception +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidCastException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidCastException.php new file mode 100644 index 00000000000..a9cff761da2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidCastException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidCastException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidDateException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidDateException.php new file mode 100644 index 00000000000..fe9d873e0d4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidDateException.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class InvalidDateException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The invalid field. + * + * @var string + */ + private $field; + + /** + * The invalid value. + * + * @var mixed + */ + private $value; + + /** + * Constructor. + * + * @param string $field + * @param mixed $value + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($field, $value, $code = 0, Throwable $previous = null) + { + $this->field = $field; + $this->value = $value; + parent::__construct($field.' : '.$value.' is not a valid value.', $code, $previous); + } + + /** + * Get the invalid field. + * + * @return string + */ + public function getField() + { + return $this->field; + } + + /** + * Get the invalid value. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidFormatException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidFormatException.php new file mode 100644 index 00000000000..27aa20116f0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidFormatException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidFormatException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidIntervalException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidIntervalException.php new file mode 100644 index 00000000000..6c95fbbce77 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidIntervalException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidIntervalException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php new file mode 100644 index 00000000000..bf54afce295 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidPeriodDateException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php new file mode 100644 index 00000000000..9e778f612f9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidPeriodParameterException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php new file mode 100644 index 00000000000..cb73e683993 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidTimeZoneException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/InvalidTypeException.php b/libraries/Carbon/src/Carbon/Exceptions/InvalidTypeException.php new file mode 100644 index 00000000000..677bdbb023c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/InvalidTypeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidTypeException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/NotACarbonClassException.php b/libraries/Carbon/src/Carbon/Exceptions/NotACarbonClassException.php new file mode 100644 index 00000000000..3710093e82c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/NotACarbonClassException.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use EDD\Vendor\Carbon\CarbonInterface; +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class NotACarbonClassException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The className. + * + * @var string + */ + protected $className; + + /** + * Constructor. + * + * @param string $className + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($className, $code = 0, Throwable $previous = null) + { + $this->className = $className; + + parent::__construct(sprintf('Given class does not implement %s: %s', CarbonInterface::class, $className), $code, $previous); + } + + /** + * Get the className. + * + * @return string + */ + public function getClassName(): string + { + return $this->className; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/NotAPeriodException.php b/libraries/Carbon/src/Carbon/Exceptions/NotAPeriodException.php new file mode 100644 index 00000000000..1489d46960d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/NotAPeriodException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class NotAPeriodException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/NotLocaleAwareException.php b/libraries/Carbon/src/Carbon/Exceptions/NotLocaleAwareException.php new file mode 100644 index 00000000000..1f093a4ba4f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/NotLocaleAwareException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class NotLocaleAwareException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param mixed $object + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($object, $code = 0, Throwable $previous = null) + { + $dump = \is_object($object) ? \get_class($object) : \gettype($object); + + parent::__construct("$dump does neither implements EDD\Vendor\Symfony\Contracts\Translation\LocaleAwareInterface nor getLocale() method.", $code, $previous); + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/OutOfRangeException.php b/libraries/Carbon/src/Carbon/Exceptions/OutOfRangeException.php new file mode 100644 index 00000000000..5fface5d558 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/OutOfRangeException.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +// This will extends OutOfRangeException instead of InvalidArgumentException since 3.0.0 +// use OutOfRangeException as BaseOutOfRangeException; + +class OutOfRangeException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The unit or name of the value. + * + * @var string + */ + private $unit; + + /** + * The range minimum. + * + * @var mixed + */ + private $min; + + /** + * The range maximum. + * + * @var mixed + */ + private $max; + + /** + * The invalid value. + * + * @var mixed + */ + private $value; + + /** + * Constructor. + * + * @param string $unit + * @param mixed $min + * @param mixed $max + * @param mixed $value + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $min, $max, $value, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + $this->min = $min; + $this->max = $max; + $this->value = $value; + + parent::__construct("$unit must be between $min and $max, $value given", $code, $previous); + } + + /** + * @return mixed + */ + public function getMax() + { + return $this->max; + } + + /** + * @return mixed + */ + public function getMin() + { + return $this->min; + } + + /** + * @return mixed + */ + public function getUnit() + { + return $this->unit; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/ParseErrorException.php b/libraries/Carbon/src/Carbon/Exceptions/ParseErrorException.php new file mode 100644 index 00000000000..12849339137 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/ParseErrorException.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class ParseErrorException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The expected. + * + * @var string + */ + protected $expected; + + /** + * The actual. + * + * @var string + */ + protected $actual; + + /** + * The help message. + * + * @var string + */ + protected $help; + + /** + * Constructor. + * + * @param string $expected + * @param string $actual + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($expected, $actual, $help = '', $code = 0, Throwable $previous = null) + { + $this->expected = $expected; + $this->actual = $actual; + $this->help = $help; + + $actual = $actual === '' ? 'data is missing' : "get '$actual'"; + + parent::__construct(trim("Format expected $expected but $actual\n$help"), $code, $previous); + } + + /** + * Get the expected. + * + * @return string + */ + public function getExpected(): string + { + return $this->expected; + } + + /** + * Get the actual. + * + * @return string + */ + public function getActual(): string + { + return $this->actual; + } + + /** + * Get the help message. + * + * @return string + */ + public function getHelp(): string + { + return $this->help; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/RuntimeException.php b/libraries/Carbon/src/Carbon/Exceptions/RuntimeException.php new file mode 100644 index 00000000000..744d5642515 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/RuntimeException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +interface RuntimeException extends Exception +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/UnitException.php b/libraries/Carbon/src/Carbon/Exceptions/UnitException.php new file mode 100644 index 00000000000..3dcae6b14f6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/UnitException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class UnitException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php b/libraries/Carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php new file mode 100644 index 00000000000..074355c36b0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use Throwable; + +class UnitNotConfiguredException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Unit $unit have no configuration to get total from other units.", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/UnknownGetterException.php b/libraries/Carbon/src/Carbon/Exceptions/UnknownGetterException.php new file mode 100644 index 00000000000..d7fb87cecf8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/UnknownGetterException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class UnknownGetterException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The getter. + * + * @var string + */ + protected $getter; + + /** + * Constructor. + * + * @param string $getter getter name + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($getter, $code = 0, Throwable $previous = null) + { + $this->getter = $getter; + + parent::__construct("Unknown getter '$getter'", $code, $previous); + } + + /** + * Get the getter. + * + * @return string + */ + public function getGetter(): string + { + return $this->getter; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/UnknownMethodException.php b/libraries/Carbon/src/Carbon/Exceptions/UnknownMethodException.php new file mode 100644 index 00000000000..00fb02f5352 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/UnknownMethodException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class UnknownMethodException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The method. + * + * @var string + */ + protected $method; + + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($method, $code = 0, Throwable $previous = null) + { + $this->method = $method; + + parent::__construct("Method $method does not exist.", $code, $previous); + } + + /** + * Get the method. + * + * @return string + */ + public function getMethod(): string + { + return $this->method; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/UnknownSetterException.php b/libraries/Carbon/src/Carbon/Exceptions/UnknownSetterException.php new file mode 100644 index 00000000000..84b34228fd1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/UnknownSetterException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class UnknownSetterException extends BaseInvalidArgumentException implements BadMethodCallException +{ + /** + * The setter. + * + * @var string + */ + protected $setter; + + /** + * Constructor. + * + * @param string $setter setter name + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($setter, $code = 0, Throwable $previous = null) + { + $this->setter = $setter; + + parent::__construct("Unknown setter '$setter'", $code, $previous); + } + + /** + * Get the setter. + * + * @return string + */ + public function getSetter(): string + { + return $this->setter; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/UnknownUnitException.php b/libraries/Carbon/src/Carbon/Exceptions/UnknownUnitException.php new file mode 100644 index 00000000000..47a5b0ba2e3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/UnknownUnitException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use Throwable; + +class UnknownUnitException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Unknown unit '$unit'.", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/libraries/Carbon/src/Carbon/Exceptions/UnreachableException.php b/libraries/Carbon/src/Carbon/Exceptions/UnreachableException.php new file mode 100644 index 00000000000..9622b64d21e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Exceptions/UnreachableException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; + +class UnreachableException extends BaseRuntimeException implements RuntimeException +{ + // +} diff --git a/libraries/Carbon/src/Carbon/Factory.php b/libraries/Carbon/src/Carbon/Factory.php new file mode 100644 index 00000000000..ec129518068 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Factory.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use Closure; +use DateTimeInterface; +use ReflectionMethod; + +/** + * A factory to generate EDD\Vendor\Carbon instances with common settings. + * + * + * + * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method EDD\Vendor\Carbon|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) Create a new EDD\Vendor\Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * @method EDD\Vendor\Carbon createFromDate($year = null, $month = null, $day = null, $tz = null) Create a EDD\Vendor\Carbon instance from just a date. The time portion is set to now. + * @method EDD\Vendor\Carbon|false createFromFormat($format, $time, $tz = null) Create a EDD\Vendor\Carbon instance from a specific format. + * @method EDD\Vendor\Carbon|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a EDD\Vendor\Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * @method EDD\Vendor\Carbon|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a EDD\Vendor\Carbon instance from a specific format and a string in a given language. + * @method EDD\Vendor\Carbon|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a EDD\Vendor\Carbon instance from a specific ISO format and a string in a given language. + * @method EDD\Vendor\Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a EDD\Vendor\Carbon instance from just a time. The date portion is set to today. + * @method EDD\Vendor\Carbon createFromTimeString($time, $tz = null) Create a EDD\Vendor\Carbon instance from a time string. The date portion is set to today. + * @method EDD\Vendor\Carbon createFromTimestamp($timestamp, $tz = null) Create a EDD\Vendor\Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method EDD\Vendor\Carbon createFromTimestampMs($timestamp, $tz = null) Create a EDD\Vendor\Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method EDD\Vendor\Carbon createFromTimestampMsUTC($timestamp) Create a EDD\Vendor\Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method EDD\Vendor\Carbon createFromTimestampUTC($timestamp) Create a EDD\Vendor\Carbon instance from an timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method EDD\Vendor\Carbon createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a EDD\Vendor\Carbon instance from just a date. The time portion is set to midnight. + * @method EDD\Vendor\Carbon|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) Create a new safe EDD\Vendor\Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new EDD\Vendor\Carbon instance from a specific date and time using strict validation. + * @method EDD\Vendor\Carbon disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method EDD\Vendor\Carbon enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method mixed executeWithLocale($locale, $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * @method EDD\Vendor\Carbon fromSerialized($value) Create an instance from a serialized string. + * @method void genericMacro($macro, $priority = 0) Register a custom macro. + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week + * @method string|null getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. + * @method int getMidDayAt() get midday/noon hour + * @method Closure|EDD\Vendor\Carbon getTestNow() Get the EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. + * @method int getWeekEndsAt() Get the last day of week + * @method int getWeekStartsAt() Get the first day of week + * @method array getWeekendDays() Get weekend days + * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasMacro($name) Checks if macro is registered globally. + * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. + * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * @method EDD\Vendor\Carbon instance($date) Create a EDD\Vendor\Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * @method bool localeHasDiffOneDayWords($locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * @method bool localeHasDiffSyntax($locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasDiffTwoDayWords($locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasShortUnits($locale) Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * @method void macro($name, $macro) Register a custom macro. + * @method EDD\Vendor\Carbon|null make($var) Make a EDD\Vendor\Carbon instance from given variable if possible. + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * @method EDD\Vendor\Carbon maxValue() Create a EDD\Vendor\Carbon instance for the greatest supported date. + * @method EDD\Vendor\Carbon minValue() Create a EDD\Vendor\Carbon instance for the lowest supported date. + * @method void mixin($mixin) Mix another object into the class. + * @method EDD\Vendor\Carbon now($tz = null) Get a EDD\Vendor\Carbon instance for the current date and time. + * @method EDD\Vendor\Carbon parse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method EDD\Vendor\Carbon parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method EDD\Vendor\Carbon|false rawCreateFromFormat($format, $time, $tz = null) Create a EDD\Vendor\Carbon instance from a specific format. + * @method EDD\Vendor\Carbon rawParse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method EDD\Vendor\Carbon resetMacros() Remove all macros and generic macros. + * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void resetToStringFormat() Reset the format used to the default when type juggling a EDD\Vendor\Carbon instance to a string + * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform EDD\Vendor\Carbon object before the serialization. + * JSON serialize all EDD\Vendor\Carbon instances using the given callback. + * @method EDD\Vendor\Carbon setFallbackLocale($locale) Set the fallback locale. + * @method EDD\Vendor\Carbon setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method EDD\Vendor\Carbon setTestNow($testNow = null) Set a EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method EDD\Vendor\Carbon setTestNowAndTimezone($testNow = null, $tz = null) Set a EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let EDD\Vendor\Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * Set the default format used when type juggling a EDD\Vendor\Carbon instance to a string. + * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. + * @method EDD\Vendor\Carbon setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * Set if UTF8 will be used for localized date/time. + * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * Set the last day of week + * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * Set the first day of week + * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * Set weekend days + * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). + * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method EDD\Vendor\Carbon today($tz = null) Create a EDD\Vendor\Carbon instance for today. + * @method EDD\Vendor\Carbon tomorrow($tz = null) Create a EDD\Vendor\Carbon instance for tomorrow. + * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method EDD\Vendor\Carbon useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method mixed withTestNow($testNow, $callback) Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * /!\ Use this method for unit tests only. + * @method EDD\Vendor\Carbon yesterday($tz = null) Create a EDD\Vendor\Carbon instance for yesterday. + * + * + */ +class Factory +{ + protected $className = Carbon::class; + + protected $settings = []; + + public function __construct(array $settings = [], ?string $className = null) + { + if ($className) { + $this->className = $className; + } + + $this->settings = $settings; + } + + public function getClassName() + { + return $this->className; + } + + public function setClassName(string $className) + { + $this->className = $className; + + return $this; + } + + public function className(string $className = null) + { + return $className === null ? $this->getClassName() : $this->setClassName($className); + } + + public function getSettings() + { + return $this->settings; + } + + public function setSettings(array $settings) + { + $this->settings = $settings; + + return $this; + } + + public function settings(array $settings = null) + { + return $settings === null ? $this->getSettings() : $this->setSettings($settings); + } + + public function mergeSettings(array $settings) + { + $this->settings = array_merge($this->settings, $settings); + + return $this; + } + + public function __call($name, $arguments) + { + $method = new ReflectionMethod($this->className, $name); + $settings = $this->settings; + + if ($settings && isset($settings['timezone'])) { + $tzParameters = array_filter($method->getParameters(), function ($parameter) { + return \in_array($parameter->getName(), ['tz', 'timezone'], true); + }); + + if (isset($arguments[0]) && \in_array($name, ['instance', 'make', 'create', 'parse'], true)) { + if ($arguments[0] instanceof DateTimeInterface) { + $settings['innerTimezone'] = $settings['timezone']; + } elseif (\is_string($arguments[0]) && date_parse($arguments[0])['is_localtime']) { + unset($settings['timezone'], $settings['innerTimezone']); + } + } elseif (\count($tzParameters)) { + array_splice($arguments, key($tzParameters), 0, [$settings['timezone']]); + unset($settings['timezone']); + } + } + + $result = $this->className::$name(...$arguments); + + return $result instanceof CarbonInterface && !empty($settings) + ? $result->settings($settings) + : $result; + } +} diff --git a/libraries/Carbon/src/Carbon/FactoryImmutable.php b/libraries/Carbon/src/Carbon/FactoryImmutable.php new file mode 100644 index 00000000000..bfd9ab4a790 --- /dev/null +++ b/libraries/Carbon/src/Carbon/FactoryImmutable.php @@ -0,0 +1,259 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use Closure; +use DateTimeImmutable; +use DateTimeZone; +use EDD\Vendor\Psr\Clock\ClockInterface; + +/** + * A factory to generate CarbonImmutable instances with common settings. + * + * + * + * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method CarbonImmutable|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) Create a new EDD\Vendor\Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * @method CarbonImmutable createFromDate($year = null, $month = null, $day = null, $tz = null) Create a EDD\Vendor\Carbon instance from just a date. The time portion is set to now. + * @method CarbonImmutable|false createFromFormat($format, $time, $tz = null) Create a EDD\Vendor\Carbon instance from a specific format. + * @method CarbonImmutable|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a EDD\Vendor\Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * @method CarbonImmutable|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a EDD\Vendor\Carbon instance from a specific format and a string in a given language. + * @method CarbonImmutable|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a EDD\Vendor\Carbon instance from a specific ISO format and a string in a given language. + * @method CarbonImmutable createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a EDD\Vendor\Carbon instance from just a time. The date portion is set to today. + * @method CarbonImmutable createFromTimeString($time, $tz = null) Create a EDD\Vendor\Carbon instance from a time string. The date portion is set to today. + * @method CarbonImmutable createFromTimestamp($timestamp, $tz = null) Create a EDD\Vendor\Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMs($timestamp, $tz = null) Create a EDD\Vendor\Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMsUTC($timestamp) Create a EDD\Vendor\Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampUTC($timestamp) Create a EDD\Vendor\Carbon instance from an timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a EDD\Vendor\Carbon instance from just a date. The time portion is set to midnight. + * @method CarbonImmutable|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) Create a new safe EDD\Vendor\Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new EDD\Vendor\Carbon instance from a specific date and time using strict validation. + * @method CarbonImmutable disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method CarbonImmutable enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method mixed executeWithLocale($locale, $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * @method CarbonImmutable fromSerialized($value) Create an instance from a serialized string. + * @method void genericMacro($macro, $priority = 0) Register a custom macro. + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week + * @method string|null getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. + * @method int getMidDayAt() get midday/noon hour + * @method Closure|CarbonImmutable getTestNow() Get the EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. + * @method int getWeekEndsAt() Get the last day of week + * @method int getWeekStartsAt() Get the first day of week + * @method array getWeekendDays() Get weekend days + * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasMacro($name) Checks if macro is registered globally. + * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. + * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * @method CarbonImmutable instance($date) Create a EDD\Vendor\Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * @method bool localeHasDiffOneDayWords($locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * @method bool localeHasDiffSyntax($locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasDiffTwoDayWords($locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasShortUnits($locale) Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * @method void macro($name, $macro) Register a custom macro. + * @method CarbonImmutable|null make($var) Make a EDD\Vendor\Carbon instance from given variable if possible. + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * @method CarbonImmutable maxValue() Create a EDD\Vendor\Carbon instance for the greatest supported date. + * @method CarbonImmutable minValue() Create a EDD\Vendor\Carbon instance for the lowest supported date. + * @method void mixin($mixin) Mix another object into the class. + * @method CarbonImmutable parse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method CarbonImmutable parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method CarbonImmutable|false rawCreateFromFormat($format, $time, $tz = null) Create a EDD\Vendor\Carbon instance from a specific format. + * @method CarbonImmutable rawParse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method CarbonImmutable resetMacros() Remove all macros and generic macros. + * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void resetToStringFormat() Reset the format used to the default when type juggling a EDD\Vendor\Carbon instance to a string + * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform EDD\Vendor\Carbon object before the serialization. + * JSON serialize all EDD\Vendor\Carbon instances using the given callback. + * @method CarbonImmutable setFallbackLocale($locale) Set the fallback locale. + * @method CarbonImmutable setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method CarbonImmutable setTestNow($testNow = null) Set a EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method CarbonImmutable setTestNowAndTimezone($testNow = null, $tz = null) Set a EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let EDD\Vendor\Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * Set the default format used when type juggling a EDD\Vendor\Carbon instance to a string. + * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. + * @method CarbonImmutable setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * Set if UTF8 will be used for localized date/time. + * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * Set the last day of week + * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * Set the first day of week + * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * Set weekend days + * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). + * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method CarbonImmutable today($tz = null) Create a EDD\Vendor\Carbon instance for today. + * @method CarbonImmutable tomorrow($tz = null) Create a EDD\Vendor\Carbon instance for tomorrow. + * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method CarbonImmutable useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method mixed withTestNow($testNow, $callback) Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * /!\ Use this method for unit tests only. + * @method CarbonImmutable yesterday($tz = null) Create a EDD\Vendor\Carbon instance for yesterday. + * + * + */ +class FactoryImmutable extends Factory implements ClockInterface +{ + protected $className = CarbonImmutable::class; + + /** + * Get a EDD\Vendor\Carbon instance for the current date and time. + * + * @param DateTimeZone|string|int|null $tz + * + * @return CarbonImmutable + */ + public function now($tz = null): DateTimeImmutable + { + $className = $this->className; + + return new $className(null, $tz); + } +} diff --git a/libraries/Carbon/src/Carbon/Lang/aa.php b/libraries/Carbon/src/Carbon/Lang/aa.php new file mode 100644 index 00000000000..e03fe1b15c2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/aa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/aa_DJ.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/aa_DJ.php b/libraries/Carbon/src/Carbon/Lang/aa_DJ.php new file mode 100644 index 00000000000..7166b5cccca --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/aa_DJ.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Qunxa Garablu', 'Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Liiqen', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['qun', 'nah', 'cig', 'agd', 'cax', 'qas', 'qad', 'leq', 'way', 'dit', 'xim', 'kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['aca', 'etl', 'tal', 'arb', 'kam', 'gum', 'sab'], + 'weekdays_min' => ['aca', 'etl', 'tal', 'arb', 'kam', 'gum', 'sab'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], + + 'year' => ':count gaqambo', // less reliable + 'y' => ':count gaqambo', // less reliable + 'a_year' => ':count gaqambo', // less reliable + + 'month' => ':count àlsa', + 'm' => ':count àlsa', + 'a_month' => ':count àlsa', + + 'day' => ':count saaku', // less reliable + 'd' => ':count saaku', // less reliable + 'a_day' => ':count saaku', // less reliable + + 'hour' => ':count ayti', // less reliable + 'h' => ':count ayti', // less reliable + 'a_hour' => ':count ayti', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/aa_ER.php b/libraries/Carbon/src/Carbon/Lang/aa_ER.php new file mode 100644 index 00000000000..136b41b1716 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/aa_ER.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Naharsi Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Leqeeni', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Nah', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/aa_ER@saaho.php b/libraries/Carbon/src/Carbon/Lang/aa_ER@saaho.php new file mode 100644 index 00000000000..07ed9463614 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/aa_ER@saaho.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Naharsi Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Leqeeni', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Nah', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Naba Sambat', 'Sani', 'Salus', 'Rabuq', 'Camus', 'Jumqata', 'Qunxa Sambat'], + 'weekdays_short' => ['Nab', 'San', 'Sal', 'Rab', 'Cam', 'Jum', 'Qun'], + 'weekdays_min' => ['Nab', 'San', 'Sal', 'Rab', 'Cam', 'Jum', 'Qun'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/aa_ET.php b/libraries/Carbon/src/Carbon/Lang/aa_ET.php new file mode 100644 index 00000000000..d968f377942 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/aa_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Liiqen', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Kud', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/af.php b/libraries/Carbon/src/Carbon/Lang/af.php new file mode 100644 index 00000000000..71c6686f9d3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/af.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Pierre du Plessis + */ +return [ + 'year' => ':count jaar', + 'a_year' => '\'n jaar|:count jaar', + 'y' => ':count j.', + 'month' => ':count maand|:count maande', + 'a_month' => '\'n maand|:count maande', + 'm' => ':count maa.', + 'week' => ':count week|:count weke', + 'a_week' => '\'n week|:count weke', + 'w' => ':count w.', + 'day' => ':count dag|:count dae', + 'a_day' => '\'n dag|:count dae', + 'd' => ':count d.', + 'hour' => ':count uur', + 'a_hour' => '\'n uur|:count uur', + 'h' => ':count u.', + 'minute' => ':count minuut|:count minute', + 'a_minute' => '\'n minuut|:count minute', + 'min' => ':count min.', + 'second' => ':count sekond|:count sekondes', + 'a_second' => '\'n paar sekondes|:count sekondes', + 's' => ':count s.', + 'ago' => ':time gelede', + 'from_now' => 'oor :time', + 'after' => ':time na', + 'before' => ':time voor', + 'diff_now' => 'Nou', + 'diff_today' => 'Vandag', + 'diff_today_regexp' => 'Vandag(?:\\s+om)?', + 'diff_yesterday' => 'Gister', + 'diff_yesterday_regexp' => 'Gister(?:\\s+om)?', + 'diff_tomorrow' => 'Môre', + 'diff_tomorrow_regexp' => 'Môre(?:\\s+om)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Vandag om] LT', + 'nextDay' => '[Môre om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[Gister om] LT', + 'lastWeek' => '[Laas] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'meridiem' => ['VM', 'NM'], + 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + 'weekdays_short' => ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'], + 'weekdays_min' => ['So', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/af_NA.php b/libraries/Carbon/src/Carbon/Lang/af_NA.php new file mode 100644 index 00000000000..887910098b0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/af_NA.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/af.php', [ + 'meridiem' => ['v', 'n'], + 'weekdays' => ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + 'weekdays_short' => ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'], + 'weekdays_min' => ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'], + 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan.', 'Feb.', 'Mrt.', 'Apr.', 'Mei', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Okt.', 'Nov.', 'Des.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'DD MMM YYYY', + 'LLL' => 'DD MMMM YYYY HH:mm', + 'LLLL' => 'dddd, DD MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/af_ZA.php b/libraries/Carbon/src/Carbon/Lang/af_ZA.php new file mode 100644 index 00000000000..b99b4cbc40c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/af_ZA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/af.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/agq.php b/libraries/Carbon/src/Carbon/Lang/agq.php new file mode 100644 index 00000000000..28983c3b127 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/agq.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a.g', 'a.k'], + 'weekdays' => ['tsuʔntsɨ', 'tsuʔukpà', 'tsuʔughɔe', 'tsuʔutɔ̀mlò', 'tsuʔumè', 'tsuʔughɨ̂m', 'tsuʔndzɨkɔʔɔ'], + 'weekdays_short' => ['nts', 'kpa', 'ghɔ', 'tɔm', 'ume', 'ghɨ', 'dzk'], + 'weekdays_min' => ['nts', 'kpa', 'ghɔ', 'tɔm', 'ume', 'ghɨ', 'dzk'], + 'months' => ['ndzɔ̀ŋɔ̀nùm', 'ndzɔ̀ŋɔ̀kƗ̀zùʔ', 'ndzɔ̀ŋɔ̀tƗ̀dʉ̀ghà', 'ndzɔ̀ŋɔ̀tǎafʉ̄ghā', 'ndzɔ̀ŋèsèe', 'ndzɔ̀ŋɔ̀nzùghò', 'ndzɔ̀ŋɔ̀dùmlo', 'ndzɔ̀ŋɔ̀kwîfɔ̀e', 'ndzɔ̀ŋɔ̀tƗ̀fʉ̀ghàdzughù', 'ndzɔ̀ŋɔ̀ghǔuwelɔ̀m', 'ndzɔ̀ŋɔ̀chwaʔàkaa wo', 'ndzɔ̀ŋèfwòo'], + 'months_short' => ['nùm', 'kɨz', 'tɨd', 'taa', 'see', 'nzu', 'dum', 'fɔe', 'dzu', 'lɔm', 'kaa', 'fwo'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/agr.php b/libraries/Carbon/src/Carbon/Lang/agr.php new file mode 100644 index 00000000000..1146bf89f71 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/agr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/agr_PE.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/agr_PE.php b/libraries/Carbon/src/Carbon/Lang/agr_PE.php new file mode 100644 index 00000000000..7c6fa96e2a6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/agr_PE.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - somosazucar.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Petsatin', 'Kupitin', 'Uyaitin', 'Tayutin', 'Kegketin', 'Tegmatin', 'Kuntutin', 'Yagkujutin', 'Daiktatin', 'Ipamtatin', 'Shinutin', 'Sakamtin'], + 'months_short' => ['Pet', 'Kup', 'Uya', 'Tay', 'Keg', 'Teg', 'Kun', 'Yag', 'Dait', 'Ipam', 'Shin', 'Sak'], + 'weekdays' => ['Tuntuamtin', 'Achutin', 'Kugkuktin', 'Saketin', 'Shimpitin', 'Imaptin', 'Bataetin'], + 'weekdays_short' => ['Tun', 'Ach', 'Kug', 'Sak', 'Shim', 'Im', 'Bat'], + 'weekdays_min' => ['Tun', 'Ach', 'Kug', 'Sak', 'Shim', 'Im', 'Bat'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 7, + 'meridiem' => ['VM', 'NM'], + + 'year' => ':count yaya', // less reliable + 'y' => ':count yaya', // less reliable + 'a_year' => ':count yaya', // less reliable + + 'month' => ':count nantu', // less reliable + 'm' => ':count nantu', // less reliable + 'a_month' => ':count nantu', // less reliable + + 'day' => ':count nayaim', // less reliable + 'd' => ':count nayaim', // less reliable + 'a_day' => ':count nayaim', // less reliable + + 'hour' => ':count kuwiš', // less reliable + 'h' => ':count kuwiš', // less reliable + 'a_hour' => ':count kuwiš', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ak.php b/libraries/Carbon/src/Carbon/Lang/ak.php new file mode 100644 index 00000000000..cf50e544717 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ak.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ak_GH.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ak_GH.php b/libraries/Carbon/src/Carbon/Lang/ak_GH.php new file mode 100644 index 00000000000..f266d8a2a08 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ak_GH.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY/MM/DD', + ], + 'months' => ['Sanda-Ɔpɛpɔn', 'Kwakwar-Ɔgyefuo', 'Ebɔw-Ɔbenem', 'Ebɔbira-Oforisuo', 'Esusow Aketseaba-Kɔtɔnimba', 'Obirade-Ayɛwohomumu', 'Ayɛwoho-Kitawonsa', 'Difuu-Ɔsandaa', 'Fankwa-Ɛbɔ', 'Ɔbɛsɛ-Ahinime', 'Ɔberɛfɛw-Obubuo', 'Mumu-Ɔpɛnimba'], + 'months_short' => ['S-Ɔ', 'K-Ɔ', 'E-Ɔ', 'E-O', 'E-K', 'O-A', 'A-K', 'D-Ɔ', 'F-Ɛ', 'Ɔ-A', 'Ɔ-O', 'M-Ɔ'], + 'weekdays' => ['Kwesida', 'Dwowda', 'Benada', 'Wukuda', 'Yawda', 'Fida', 'Memeneda'], + 'weekdays_short' => ['Kwe', 'Dwo', 'Ben', 'Wuk', 'Yaw', 'Fia', 'Mem'], + 'weekdays_min' => ['Kwe', 'Dwo', 'Ben', 'Wuk', 'Yaw', 'Fia', 'Mem'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['AN', 'EW'], + + 'year' => ':count afe', + 'y' => ':count afe', + 'a_year' => ':count afe', + + 'month' => ':count bosume', + 'm' => ':count bosume', + 'a_month' => ':count bosume', + + 'day' => ':count ɛda', + 'd' => ':count ɛda', + 'a_day' => ':count ɛda', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/am.php b/libraries/Carbon/src/Carbon/Lang/am.php new file mode 100644 index 00000000000..4366b6a1f2d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/am.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/am_ET.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/am_ET.php b/libraries/Carbon/src/Carbon/Lang/am_ET.php new file mode 100644 index 00000000000..ec469b37018 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/am_ET.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕሪል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክቶበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['እሑድ', 'ሰኞ', 'ማክሰኞ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'weekdays_short' => ['እሑድ', 'ሰኞ ', 'ማክሰ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'weekdays_min' => ['እሑድ', 'ሰኞ ', 'ማክሰ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጡዋት', 'ከሰዓት'], + + 'year' => ':count አመት', + 'y' => ':count አመት', + 'a_year' => ':count አመት', + + 'month' => ':count ወር', + 'm' => ':count ወር', + 'a_month' => ':count ወር', + + 'week' => ':count ሳምንት', + 'w' => ':count ሳምንት', + 'a_week' => ':count ሳምንት', + + 'day' => ':count ቀን', + 'd' => ':count ቀን', + 'a_day' => ':count ቀን', + + 'hour' => ':count ሰዓት', + 'h' => ':count ሰዓት', + 'a_hour' => ':count ሰዓት', + + 'minute' => ':count ደቂቃ', + 'min' => ':count ደቂቃ', + 'a_minute' => ':count ደቂቃ', + + 'second' => ':count ሴኮንድ', + 's' => ':count ሴኮንድ', + 'a_second' => ':count ሴኮንድ', + + 'ago' => 'ከ:time በፊት', + 'from_now' => 'በ:time ውስጥ', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/an.php b/libraries/Carbon/src/Carbon/Lang/an.php new file mode 100644 index 00000000000..96b39526b89 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/an.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/an_ES.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/an_ES.php b/libraries/Carbon/src/Carbon/Lang/an_ES.php new file mode 100644 index 00000000000..871cefc06e9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/an_ES.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Softaragones Jordi Mallach Pérez, Juan Pablo Martínez bug-glibc-locales@gnu.org, softaragones@softaragones.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['chinero', 'febrero', 'marzo', 'abril', 'mayo', 'chunyo', 'chuliol', 'agosto', 'setiembre', 'octubre', 'noviembre', 'aviento'], + 'months_short' => ['chi', 'feb', 'mar', 'abr', 'may', 'chn', 'chl', 'ago', 'set', 'oct', 'nov', 'avi'], + 'weekdays' => ['domingo', 'luns', 'martes', 'mierques', 'chueves', 'viernes', 'sabado'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mie', 'chu', 'vie', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mie', 'chu', 'vie', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count año', + 'y' => ':count año', + 'a_year' => ':count año', + + 'month' => ':count mes', + 'm' => ':count mes', + 'a_month' => ':count mes', + + 'week' => ':count semana', + 'w' => ':count semana', + 'a_week' => ':count semana', + + 'day' => ':count día', + 'd' => ':count día', + 'a_day' => ':count día', + + 'hour' => ':count reloch', // less reliable + 'h' => ':count reloch', // less reliable + 'a_hour' => ':count reloch', // less reliable + + 'minute' => ':count minuto', + 'min' => ':count minuto', + 'a_minute' => ':count minuto', + + 'second' => ':count segundo', + 's' => ':count segundo', + 'a_second' => ':count segundo', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/anp.php b/libraries/Carbon/src/Carbon/Lang/anp.php new file mode 100644 index 00000000000..d41526ee51e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/anp.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/anp_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/anp_IN.php b/libraries/Carbon/src/Carbon/Lang/anp_IN.php new file mode 100644 index 00000000000..ab6f70462d6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/anp_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर"'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'बृहस्पतिवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar.php b/libraries/Carbon/src/Carbon/Lang/ar.php new file mode 100644 index 00000000000..666ec17aa8a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Atef Ben Ali (atefBB) + * - Ibrahim AshShohail + * - MLTDev + * - Mohamed Sabil (mohamedsabil83) + * - Yazan Alnugnugh (yazan-alnugnugh) + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => ':time من الآن', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ar_AE.php b/libraries/Carbon/src/Carbon/Lang/ar_AE.php new file mode 100644 index 00000000000..7cc6dfa9c7f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_AE.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت '], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_BH.php b/libraries/Carbon/src/Carbon/Lang/ar_BH.php new file mode 100644 index 00000000000..a71a56cc642 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_BH.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_DJ.php b/libraries/Carbon/src/Carbon/Lang/ar_DJ.php new file mode 100644 index 00000000000..c68652f96e1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_DJ.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_DZ.php b/libraries/Carbon/src/Carbon/Lang/ar_DZ.php new file mode 100644 index 00000000000..6e2a2bcacdb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_DZ.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - Noureddine LOUAHEDJ + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'جانفي', + 'فيفري', + 'مارس', + 'أفريل', + 'ماي', + 'جوان', + 'جويلية', + 'أوت', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['أح', 'إث', 'ثلا', 'أر', 'خم', 'جم', 'سب'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ar_EG.php b/libraries/Carbon/src/Carbon/Lang/ar_EG.php new file mode 100644 index 00000000000..a71a56cc642 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_EG.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_EH.php b/libraries/Carbon/src/Carbon/Lang/ar_EH.php new file mode 100644 index 00000000000..c68652f96e1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_EH.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_ER.php b/libraries/Carbon/src/Carbon/Lang/ar_ER.php new file mode 100644 index 00000000000..c68652f96e1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_ER.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_IL.php b/libraries/Carbon/src/Carbon/Lang/ar_IL.php new file mode 100644 index 00000000000..c68652f96e1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_IL.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_IN.php b/libraries/Carbon/src/Carbon/Lang/ar_IN.php new file mode 100644 index 00000000000..879f2188f34 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_IQ.php b/libraries/Carbon/src/Carbon/Lang/ar_IQ.php new file mode 100644 index 00000000000..959b45105a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_IQ.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_JO.php b/libraries/Carbon/src/Carbon/Lang/ar_JO.php new file mode 100644 index 00000000000..959b45105a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_JO.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_KM.php b/libraries/Carbon/src/Carbon/Lang/ar_KM.php new file mode 100644 index 00000000000..c68652f96e1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_KM.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_KW.php b/libraries/Carbon/src/Carbon/Lang/ar_KW.php new file mode 100644 index 00000000000..1fcf027061f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_KW.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - Nusret Parlak + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + * - Abdullah-Alhariri + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'ماي', + 'يونيو', + 'يوليوز', + 'غشت', + 'شتنبر', + 'أكتوبر', + 'نونبر', + 'دجنبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ar_LB.php b/libraries/Carbon/src/Carbon/Lang/ar_LB.php new file mode 100644 index 00000000000..686ed87308e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_LB.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_LY.php b/libraries/Carbon/src/Carbon/Lang/ar_LY.php new file mode 100644 index 00000000000..be259c710b9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_LY.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Atef Ben Ali (atefBB) + * - Ibrahim AshShohail + * - MLTDev + */ + +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => ':time من الآن', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['مرة', 'مرة', ':count مرتين', ':count مرات', ':count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ar_MA.php b/libraries/Carbon/src/Carbon/Lang/ar_MA.php new file mode 100644 index 00000000000..0de70db60d0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_MA.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'ماي', + 'يونيو', + 'يوليوز', + 'غشت', + 'شتنبر', + 'أكتوبر', + 'نونبر', + 'دجنبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ar_MR.php b/libraries/Carbon/src/Carbon/Lang/ar_MR.php new file mode 100644 index 00000000000..c68652f96e1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_MR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_OM.php b/libraries/Carbon/src/Carbon/Lang/ar_OM.php new file mode 100644 index 00000000000..a71a56cc642 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_OM.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_PS.php b/libraries/Carbon/src/Carbon/Lang/ar_PS.php new file mode 100644 index 00000000000..70f83f25fe8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_PS.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_QA.php b/libraries/Carbon/src/Carbon/Lang/ar_QA.php new file mode 100644 index 00000000000..a71a56cc642 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_QA.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_SA.php b/libraries/Carbon/src/Carbon/Lang/ar_SA.php new file mode 100644 index 00000000000..d53ee08ef50 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_SA.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + * - Abdullah-Alhariri + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ar_SD.php b/libraries/Carbon/src/Carbon/Lang/ar_SD.php new file mode 100644 index 00000000000..a71a56cc642 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_SD.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_SO.php b/libraries/Carbon/src/Carbon/Lang/ar_SO.php new file mode 100644 index 00000000000..c68652f96e1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_SO.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_SS.php b/libraries/Carbon/src/Carbon/Lang/ar_SS.php new file mode 100644 index 00000000000..319d435b0ce --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_SS.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_SY.php b/libraries/Carbon/src/Carbon/Lang/ar_SY.php new file mode 100644 index 00000000000..959b45105a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_SY.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_Shakl.php b/libraries/Carbon/src/Carbon/Lang/ar_Shakl.php new file mode 100644 index 00000000000..fbb677785a8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_Shakl.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Abdellah Chadidi + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +// Same for long and short +$months = [ + // @TODO add shakl to months + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سَنَة', '{1}سَنَة', '{2}سَنَتَيْن', ']2,11[:count سَنَوَات', ']10,Inf[:count سَنَة']), + 'a_year' => implode('|', ['{0}:count سَنَة', '{1}سَنَة', '{2}سَنَتَيْن', ']2,11[:count سَنَوَات', ']10,Inf[:count سَنَة']), + 'month' => implode('|', ['{0}:count شَهْرَ', '{1}شَهْرَ', '{2}شَهْرَيْن', ']2,11[:count أَشْهُر', ']10,Inf[:count شَهْرَ']), + 'a_month' => implode('|', ['{0}:count شَهْرَ', '{1}شَهْرَ', '{2}شَهْرَيْن', ']2,11[:count أَشْهُر', ']10,Inf[:count شَهْرَ']), + 'week' => implode('|', ['{0}:count أُسْبُوع', '{1}أُسْبُوع', '{2}أُسْبُوعَيْن', ']2,11[:count أَسَابِيع', ']10,Inf[:count أُسْبُوع']), + 'a_week' => implode('|', ['{0}:count أُسْبُوع', '{1}أُسْبُوع', '{2}أُسْبُوعَيْن', ']2,11[:count أَسَابِيع', ']10,Inf[:count أُسْبُوع']), + 'day' => implode('|', ['{0}:count يَوْم', '{1}يَوْم', '{2}يَوْمَيْن', ']2,11[:count أَيَّام', ']10,Inf[:count يَوْم']), + 'a_day' => implode('|', ['{0}:count يَوْم', '{1}يَوْم', '{2}يَوْمَيْن', ']2,11[:count أَيَّام', ']10,Inf[:count يَوْم']), + 'hour' => implode('|', ['{0}:count سَاعَة', '{1}سَاعَة', '{2}سَاعَتَيْن', ']2,11[:count سَاعَات', ']10,Inf[:count سَاعَة']), + 'a_hour' => implode('|', ['{0}:count سَاعَة', '{1}سَاعَة', '{2}سَاعَتَيْن', ']2,11[:count سَاعَات', ']10,Inf[:count سَاعَة']), + 'minute' => implode('|', ['{0}:count دَقِيقَة', '{1}دَقِيقَة', '{2}دَقِيقَتَيْن', ']2,11[:count دَقَائِق', ']10,Inf[:count دَقِيقَة']), + 'a_minute' => implode('|', ['{0}:count دَقِيقَة', '{1}دَقِيقَة', '{2}دَقِيقَتَيْن', ']2,11[:count دَقَائِق', ']10,Inf[:count دَقِيقَة']), + 'second' => implode('|', ['{0}:count ثَانِيَة', '{1}ثَانِيَة', '{2}ثَانِيَتَيْن', ']2,11[:count ثَوَان', ']10,Inf[:count ثَانِيَة']), + 'a_second' => implode('|', ['{0}:count ثَانِيَة', '{1}ثَانِيَة', '{2}ثَانِيَتَيْن', ']2,11[:count ثَوَان', ']10,Inf[:count ثَانِيَة']), + 'ago' => 'مُنْذُ :time', + 'from_now' => 'مِنَ الْآن :time', + 'after' => 'بَعْدَ :time', + 'before' => 'قَبْلَ :time', + + // @TODO add shakl to translations below + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ar_TD.php b/libraries/Carbon/src/Carbon/Lang/ar_TD.php new file mode 100644 index 00000000000..c68652f96e1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_TD.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ar_TN.php b/libraries/Carbon/src/Carbon/Lang/ar_TN.php new file mode 100644 index 00000000000..f79725a4a60 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_TN.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'جانفي', + 'فيفري', + 'مارس', + 'أفريل', + 'ماي', + 'جوان', + 'جويلية', + 'أوت', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ar_YE.php b/libraries/Carbon/src/Carbon/Lang/ar_YE.php new file mode 100644 index 00000000000..54bd6f07237 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ar_YE.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰٤', '۰٥', '۰٦', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱٤', '۱٥', '۱٦', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲٤', '۲٥', '۲٦', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳٤', '۳٥', '۳٦', '۳۷', '۳۸', '۳۹', '٤۰', '٤۱', '٤۲', '٤۳', '٤٤', '٤٥', '٤٦', '٤۷', '٤۸', '٤۹', '٥۰', '٥۱', '٥۲', '٥۳', '٥٤', '٥٥', '٥٦', '٥۷', '٥۸', '٥۹', '٦۰', '٦۱', '٦۲', '٦۳', '٦٤', '٦٥', '٦٦', '٦۷', '٦۸', '٦۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷٤', '۷٥', '۷٦', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸٤', '۸٥', '۸٦', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹٤', '۹٥', '۹٦', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/as.php b/libraries/Carbon/src/Carbon/Lang/as.php new file mode 100644 index 00000000000..1292245cfc7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/as.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/as_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/as_IN.php b/libraries/Carbon/src/Carbon/Lang/as_IN.php new file mode 100644 index 00000000000..97912992b8c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/as_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Amitakhya Phukan, Red Hat bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-MM-YYYY', + ], + 'months' => ['জানুৱাৰী', 'ফেব্ৰুৱাৰী', 'মাৰ্চ', 'এপ্ৰিল', 'মে', 'জুন', 'জুলাই', 'আগষ্ট', 'ছেপ্তেম্বৰ', 'অক্টোবৰ', 'নৱেম্বৰ', 'ডিচেম্বৰ'], + 'months_short' => ['জানু', 'ফেব্ৰু', 'মাৰ্চ', 'এপ্ৰিল', 'মে', 'জুন', 'জুলাই', 'আগ', 'সেপ্ট', 'অক্টো', 'নভে', 'ডিসে'], + 'weekdays' => ['দেওবাৰ', 'সোমবাৰ', 'মঙ্গলবাৰ', 'বুধবাৰ', 'বৃহষ্পতিবাৰ', 'শুক্ৰবাৰ', 'শনিবাৰ'], + 'weekdays_short' => ['দেও', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহষ্পতি', 'শুক্ৰ', 'শনি'], + 'weekdays_min' => ['দেও', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহষ্পতি', 'শুক্ৰ', 'শনি'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['পূৰ্ব্বাহ্ন', 'অপৰাহ্ন'], + + 'year' => ':count বছৰ', + 'y' => ':count বছৰ', + 'a_year' => ':count বছৰ', + + 'month' => ':count মাহ', + 'm' => ':count মাহ', + 'a_month' => ':count মাহ', + + 'week' => ':count সপ্তাহ', + 'w' => ':count সপ্তাহ', + 'a_week' => ':count সপ্তাহ', + + 'day' => ':count বাৰ', + 'd' => ':count বাৰ', + 'a_day' => ':count বাৰ', + + 'hour' => ':count ঘণ্টা', + 'h' => ':count ঘণ্টা', + 'a_hour' => ':count ঘণ্টা', + + 'minute' => ':count মিনিট', + 'min' => ':count মিনিট', + 'a_minute' => ':count মিনিট', + + 'second' => ':count দ্বিতীয়', + 's' => ':count দ্বিতীয়', + 'a_second' => ':count দ্বিতীয়', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/asa.php b/libraries/Carbon/src/Carbon/Lang/asa.php new file mode 100644 index 00000000000..c5e13137162 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/asa.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['icheheavo', 'ichamthi'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Ijm', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Ijm', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Dec'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ast.php b/libraries/Carbon/src/Carbon/Lang/ast.php new file mode 100644 index 00000000000..b9828c69017 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ast.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Jordi Mallach jordi@gnu.org + * - Adolfo Jayme-Barrientos (fitojb) + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['de xineru', 'de febreru', 'de marzu', 'd’abril', 'de mayu', 'de xunu', 'de xunetu', 'd’agostu', 'de setiembre', 'd’ochobre', 'de payares', 'd’avientu'], + 'months_short' => ['xin', 'feb', 'mar', 'abr', 'may', 'xun', 'xnt', 'ago', 'set', 'och', 'pay', 'avi'], + 'weekdays' => ['domingu', 'llunes', 'martes', 'miércoles', 'xueves', 'vienres', 'sábadu'], + 'weekdays_short' => ['dom', 'llu', 'mar', 'mié', 'xue', 'vie', 'sáb'], + 'weekdays_min' => ['dom', 'llu', 'mar', 'mié', 'xue', 'vie', 'sáb'], + + 'year' => ':count añu|:count años', + 'y' => ':count añu|:count años', + 'a_year' => 'un añu|:count años', + + 'month' => ':count mes', + 'm' => ':count mes', + 'a_month' => 'un mes|:count mes', + + 'week' => ':count selmana|:count selmanes', + 'w' => ':count selmana|:count selmanes', + 'a_week' => 'una selmana|:count selmanes', + + 'day' => ':count día|:count díes', + 'd' => ':count día|:count díes', + 'a_day' => 'un día|:count díes', + + 'hour' => ':count hora|:count hores', + 'h' => ':count hora|:count hores', + 'a_hour' => 'una hora|:count hores', + + 'minute' => ':count minutu|:count minutos', + 'min' => ':count minutu|:count minutos', + 'a_minute' => 'un minutu|:count minutos', + + 'second' => ':count segundu|:count segundos', + 's' => ':count segundu|:count segundos', + 'a_second' => 'un segundu|:count segundos', + + 'ago' => 'hai :time', + 'from_now' => 'en :time', + 'after' => ':time dempués', + 'before' => ':time enantes', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ast_ES.php b/libraries/Carbon/src/Carbon/Lang/ast_ES.php new file mode 100644 index 00000000000..f3e82c368f2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ast_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ast.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ayc.php b/libraries/Carbon/src/Carbon/Lang/ayc.php new file mode 100644 index 00000000000..24988f55a38 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ayc.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ayc_PE.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ayc_PE.php b/libraries/Carbon/src/Carbon/Lang/ayc_PE.php new file mode 100644 index 00000000000..87d9c4db21e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ayc_PE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - runasimipi.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['inïru', 'phiwriru', 'marsu', 'awrila', 'mayu', 'junyu', 'julyu', 'awustu', 'sitimri', 'uktuwri', 'nuwimri', 'risimri'], + 'months_short' => ['ini', 'phi', 'mar', 'awr', 'may', 'jun', 'jul', 'awu', 'sit', 'ukt', 'nuw', 'ris'], + 'weekdays' => ['tuminku', 'lunisa', 'martisa', 'mirkulisa', 'juywisa', 'wirnisa', 'sawäru'], + 'weekdays_short' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'weekdays_min' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['VM', 'NM'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/az.php b/libraries/Carbon/src/Carbon/Lang/az.php new file mode 100644 index 00000000000..1be5b1fcf1f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/az.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Kunal Marwaha + * - François B + * - JD Isaacks + * - Orxan + * - Şəhriyar İmanov + * - Baran Şengül + */ +return [ + 'year' => ':count il', + 'a_year' => '{1}bir il|]1,Inf[:count il', + 'y' => ':count il', + 'month' => ':count ay', + 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'm' => ':count ay', + 'week' => ':count həftə', + 'a_week' => '{1}bir həftə|]1,Inf[:count həftə', + 'w' => ':count h.', + 'day' => ':count gün', + 'a_day' => '{1}bir gün|]1,Inf[:count gün', + 'd' => ':count g.', + 'hour' => ':count saat', + 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'h' => ':count saat', + 'minute' => ':count d.', + 'a_minute' => '{1}bir dəqiqə|]1,Inf[:count dəqiqə', + 'min' => ':count dəqiqə', + 'second' => ':count san.', + 'a_second' => '{1}birneçə saniyə|]1,Inf[:count saniyə', + 's' => ':count saniyə', + 'ago' => ':time əvvəl', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time əvvəl', + 'diff_now' => 'indi', + 'diff_today' => 'bugün', + 'diff_today_regexp' => 'bugün(?:\\s+saat)?', + 'diff_yesterday' => 'dünən', + 'diff_tomorrow' => 'sabah', + 'diff_tomorrow_regexp' => 'sabah(?:\\s+saat)?', + 'diff_before_yesterday' => 'srağagün', + 'diff_after_tomorrow' => 'birisi gün', + 'period_recurrences' => ':count dəfədən bir', + 'period_interval' => 'hər :interval', + 'period_start_date' => ':date tarixindən başlayaraq', + 'period_end_date' => ':date tarixinədək', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[bugün saat] LT', + 'nextDay' => '[sabah saat] LT', + 'nextWeek' => '[gələn həftə] dddd [saat] LT', + 'lastDay' => '[dünən] LT', + 'lastWeek' => '[keçən həftə] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { // special case for zero + return "$number-ıncı"; + } + + static $suffixes = [ + 1 => '-inci', + 5 => '-inci', + 8 => '-inci', + 70 => '-inci', + 80 => '-inci', + 2 => '-nci', + 7 => '-nci', + 20 => '-nci', + 50 => '-nci', + 3 => '-üncü', + 4 => '-üncü', + 100 => '-üncü', + 6 => '-ncı', + 9 => '-uncu', + 10 => '-uncu', + 30 => '-uncu', + 60 => '-ıncı', + 90 => '-ıncı', + ]; + + $lastDigit = $number % 10; + + return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'gecə'; + } + if ($hour < 12) { + return 'səhər'; + } + if ($hour < 17) { + return 'gündüz'; + } + + return 'axşam'; + }, + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], + 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], + 'weekdays' => ['bazar', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['baz', 'bze', 'çax', 'çər', 'cax', 'cüm', 'şən'], + 'weekdays_min' => ['bz', 'be', 'ça', 'çə', 'ca', 'cü', 'şə'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' və '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/az_AZ.php b/libraries/Carbon/src/Carbon/Lang/az_AZ.php new file mode 100644 index 00000000000..f9fb4e05099 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/az_AZ.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/az.php', [ + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['baz', 'ber', 'çax', 'çər', 'cax', 'cüm', 'şnb'], + 'weekdays_min' => ['baz', 'ber', 'çax', 'çər', 'cax', 'cüm', 'şnb'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/az_Cyrl.php b/libraries/Carbon/src/Carbon/Lang/az_Cyrl.php new file mode 100644 index 00000000000..ae908e11afe --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/az_Cyrl.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/az.php', [ + 'weekdays' => ['базар', 'базар ертәси', 'чәршәнбә ахшамы', 'чәршәнбә', 'ҹүмә ахшамы', 'ҹүмә', 'шәнбә'], + 'weekdays_short' => ['Б.', 'Б.Е.', 'Ч.А.', 'Ч.', 'Ҹ.А.', 'Ҹ.', 'Ш.'], + 'weekdays_min' => ['Б.', 'Б.Е.', 'Ч.А.', 'Ч.', 'Ҹ.А.', 'Ҹ.', 'Ш.'], + 'months' => ['јанвар', 'феврал', 'март', 'апрел', 'май', 'ијун', 'ијул', 'август', 'сентјабр', 'октјабр', 'нојабр', 'декабр'], + 'months_short' => ['јан', 'фев', 'мар', 'апр', 'май', 'ијн', 'ијл', 'авг', 'сен', 'окт', 'ној', 'дек'], + 'months_standalone' => ['Јанвар', 'Феврал', 'Март', 'Апрел', 'Май', 'Ијун', 'Ијул', 'Август', 'Сентјабр', 'Октјабр', 'Нојабр', 'Декабр'], + 'meridiem' => ['а', 'п'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/az_IR.php b/libraries/Carbon/src/Carbon/Lang/az_IR.php new file mode 100644 index 00000000000..9248e752f73 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/az_IR.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mousa Moradi mousamk@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY/OM/OD', + ], + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مئی', 'ژوئن', 'جولای', 'آقۇست', 'سپتامبر', 'اوْکتوْبر', 'نوْوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مئی', 'ژوئن', 'جولای', 'آقۇست', 'سپتامبر', 'اوْکتوْبر', 'نوْوامبر', 'دسامبر'], + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'weekdays_short' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'weekdays_min' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'first_day_of_week' => 6, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰۴', '۰۵', '۰۶', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱۴', '۱۵', '۱۶', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲۴', '۲۵', '۲۶', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳۴', '۳۵', '۳۶', '۳۷', '۳۸', '۳۹', '۴۰', '۴۱', '۴۲', '۴۳', '۴۴', '۴۵', '۴۶', '۴۷', '۴۸', '۴۹', '۵۰', '۵۱', '۵۲', '۵۳', '۵۴', '۵۵', '۵۶', '۵۷', '۵۸', '۵۹', '۶۰', '۶۱', '۶۲', '۶۳', '۶۴', '۶۵', '۶۶', '۶۷', '۶۸', '۶۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷۴', '۷۵', '۷۶', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸۴', '۸۵', '۸۶', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹۴', '۹۵', '۹۶', '۹۷', '۹۸', '۹۹'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/az_Latn.php b/libraries/Carbon/src/Carbon/Lang/az_Latn.php new file mode 100644 index 00000000000..ace539b84b7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/az_Latn.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/az.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['bazar', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['B.', 'B.E.', 'Ç.A.', 'Ç.', 'C.A.', 'C.', 'Ş.'], + 'weekdays_min' => ['B.', 'B.E.', 'Ç.A.', 'Ç.', 'C.A.', 'C.', 'Ş.'], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], + 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bas.php b/libraries/Carbon/src/Carbon/Lang/bas.php new file mode 100644 index 00000000000..0516cfa3398 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bas.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['I bikɛ̂glà', 'I ɓugajɔp'], + 'weekdays' => ['ŋgwà nɔ̂y', 'ŋgwà njaŋgumba', 'ŋgwà ûm', 'ŋgwà ŋgê', 'ŋgwà mbɔk', 'ŋgwà kɔɔ', 'ŋgwà jôn'], + 'weekdays_short' => ['nɔy', 'nja', 'uum', 'ŋge', 'mbɔ', 'kɔɔ', 'jon'], + 'weekdays_min' => ['nɔy', 'nja', 'uum', 'ŋge', 'mbɔ', 'kɔɔ', 'jon'], + 'months' => ['Kɔndɔŋ', 'Màcɛ̂l', 'Màtùmb', 'Màtop', 'M̀puyɛ', 'Hìlòndɛ̀', 'Njèbà', 'Hìkaŋ', 'Dìpɔ̀s', 'Bìòôm', 'Màyɛsèp', 'Lìbuy li ńyèe'], + 'months_short' => ['kɔn', 'mac', 'mat', 'mto', 'mpu', 'hil', 'nje', 'hik', 'dip', 'bio', 'may', 'liɓ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'second' => ':count móndî', // less reliable + 's' => ':count móndî', // less reliable + 'a_second' => ':count móndî', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/be.php b/libraries/Carbon/src/Carbon/Lang/be.php new file mode 100644 index 00000000000..1a91b0111d7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/be.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + }, 'be'); +} +// @codeCoverageIgnoreEnd + +/* + * Authors: + * - Josh Soref + * - SobakaSlava + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - AbadonnaAbbys + * - Siomkin Alexander + */ +return [ + 'year' => ':count год|:count гады|:count гадоў', + 'a_year' => '{1}год|:count год|:count гады|:count гадоў', + 'y' => ':count год|:count гады|:count гадоў', + 'month' => ':count месяц|:count месяцы|:count месяцаў', + 'a_month' => '{1}месяц|:count месяц|:count месяцы|:count месяцаў', + 'm' => ':count месяц|:count месяцы|:count месяцаў', + 'week' => ':count тыдзень|:count тыдні|:count тыдняў', + 'a_week' => '{1}тыдзень|:count тыдзень|:count тыдні|:count тыдняў', + 'w' => ':count тыдзень|:count тыдні|:count тыдняў', + 'day' => ':count дзень|:count дні|:count дзён', + 'a_day' => '{1}дзень|:count дзень|:count дні|:count дзён', + 'd' => ':count дн', + 'hour' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour' => '{1}гадзіна|:count гадзіна|:count гадзіны|:count гадзін', + 'h' => ':count гадзіна|:count гадзіны|:count гадзін', + 'minute' => ':count хвіліна|:count хвіліны|:count хвілін', + 'a_minute' => '{1}хвіліна|:count хвіліна|:count хвіліны|:count хвілін', + 'min' => ':count хв', + 'second' => ':count секунда|:count секунды|:count секунд', + 'a_second' => '{1}некалькі секунд|:count секунда|:count секунды|:count секунд', + 's' => ':count сек', + + 'hour_ago' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_ago' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_ago' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_ago' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_ago' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_ago' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_ago' => ':count секунду|:count секунды|:count секунд', + 'a_second_ago' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_ago' => ':count секунду|:count секунды|:count секунд', + + 'hour_from_now' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_from_now' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_from_now' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_from_now' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_from_now' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_from_now' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_from_now' => ':count секунду|:count секунды|:count секунд', + 'a_second_from_now' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_from_now' => ':count секунду|:count секунды|:count секунд', + + 'hour_after' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_after' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_after' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_after' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_after' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_after' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_after' => ':count секунду|:count секунды|:count секунд', + 'a_second_after' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_after' => ':count секунду|:count секунды|:count секунд', + + 'hour_before' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_before' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_before' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_before' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_before' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_before' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_before' => ':count секунду|:count секунды|:count секунд', + 'a_second_before' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_before' => ':count секунду|:count секунды|:count секунд', + + 'ago' => ':time таму', + 'from_now' => 'праз :time', + 'after' => ':time пасля', + 'before' => ':time да', + 'diff_now' => 'цяпер', + 'diff_today' => 'Сёння', + 'diff_today_regexp' => 'Сёння(?:\\s+ў)?', + 'diff_yesterday' => 'учора', + 'diff_yesterday_regexp' => 'Учора(?:\\s+ў)?', + 'diff_tomorrow' => 'заўтра', + 'diff_tomorrow_regexp' => 'Заўтра(?:\\s+ў)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY г.', + 'LLL' => 'D MMMM YYYY г., HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY г., HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Сёння ў] LT', + 'nextDay' => '[Заўтра ў] LT', + 'nextWeek' => '[У] dddd [ў] LT', + 'lastDay' => '[Учора ў] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 1: + case 2: + case 4: + return '[У мінулы] dddd [ў] LT'; + default: + return '[У мінулую] dddd [ў] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return ($number % 10 === 2 || $number % 10 === 3) && ($number % 100 !== 12 && $number % 100 !== 13) ? $number.'-і' : $number.'-ы'; + case 'D': + return $number.'-га'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночы'; + } + if ($hour < 12) { + return 'раніцы'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечара'; + }, + 'months' => ['студзеня', 'лютага', 'сакавіка', 'красавіка', 'траўня', 'чэрвеня', 'ліпеня', 'жніўня', 'верасня', 'кастрычніка', 'лістапада', 'снежня'], + 'months_standalone' => ['студзень', 'люты', 'сакавік', 'красавік', 'травень', 'чэрвень', 'ліпень', 'жнівень', 'верасень', 'кастрычнік', 'лістапад', 'снежань'], + 'months_short' => ['студ', 'лют', 'сак', 'крас', 'трав', 'чэрв', 'ліп', 'жнів', 'вер', 'каст', 'ліст', 'снеж'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['нядзелю', 'панядзелак', 'аўторак', 'сераду', 'чацвер', 'пятніцу', 'суботу'], + 'weekdays_standalone' => ['нядзеля', 'панядзелак', 'аўторак', 'серада', 'чацвер', 'пятніца', 'субота'], + 'weekdays_short' => ['нд', 'пн', 'ат', 'ср', 'чц', 'пт', 'сб'], + 'weekdays_min' => ['нд', 'пн', 'ат', 'ср', 'чц', 'пт', 'сб'], + 'weekdays_regexp' => '/\[ ?[Ууў] ?(?:мінулую|наступную)? ?\] ?dddd/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' і '], + 'months_short_standalone' => ['сту', 'лют', 'сак', 'кра', 'май', 'чэр', 'ліп', 'жні', 'вер', 'кас', 'ліс', 'сне'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/be_BY.php b/libraries/Carbon/src/Carbon/Lang/be_BY.php new file mode 100644 index 00000000000..9bab0f66fb7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/be_BY.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/be.php', [ + 'months' => ['студзеня', 'лютага', 'сакавіка', 'красавіка', 'мая', 'чэрвеня', 'ліпеня', 'жніўня', 'верасня', 'кастрычніка', 'лістапада', 'снежня'], + 'months_short' => ['сту', 'лют', 'сак', 'кра', 'мая', 'чэр', 'ліп', 'жні', 'вер', 'кас', 'ліс', 'сне'], + 'weekdays' => ['Нядзеля', 'Панядзелак', 'Аўторак', 'Серада', 'Чацвер', 'Пятніца', 'Субота'], + 'weekdays_short' => ['Няд', 'Пан', 'Аўт', 'Срд', 'Чцв', 'Пят', 'Суб'], + 'weekdays_min' => ['Няд', 'Пан', 'Аўт', 'Срд', 'Чцв', 'Пят', 'Суб'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/be_BY@latin.php b/libraries/Carbon/src/Carbon/Lang/be_BY@latin.php new file mode 100644 index 00000000000..54035031f35 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/be_BY@latin.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['studzienia', 'lutaha', 'sakavika', 'krasavika', 'maja', 'červienia', 'lipienia', 'žniŭnia', 'vieraśnia', 'kastryčnika', 'listapada', 'śniežnia'], + 'months_short' => ['Stu', 'Lut', 'Sak', 'Kra', 'Maj', 'Čer', 'Lip', 'Žni', 'Vie', 'Kas', 'Lis', 'Śni'], + 'weekdays' => ['Niadziela', 'Paniadziełak', 'Aŭtorak', 'Sierada', 'Čaćvier', 'Piatnica', 'Subota'], + 'weekdays_short' => ['Nia', 'Pan', 'Aŭt', 'Sie', 'Čać', 'Pia', 'Sub'], + 'weekdays_min' => ['Nia', 'Pan', 'Aŭt', 'Sie', 'Čać', 'Pia', 'Sub'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bem.php b/libraries/Carbon/src/Carbon/Lang/bem.php new file mode 100644 index 00000000000..0cb551a2e37 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bem.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bem_ZM.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/bem_ZM.php b/libraries/Carbon/src/Carbon/Lang/bem_ZM.php new file mode 100644 index 00000000000..11a59085ed3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bem_ZM.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANLoc Martin Benjamin locales@africanlocalization.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Epreo', 'Mei', 'Juni', 'Julai', 'Ogasti', 'Septemba', 'Oktoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Epr', 'Mei', 'Jun', 'Jul', 'Oga', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['Pa Mulungu', 'Palichimo', 'Palichibuli', 'Palichitatu', 'Palichine', 'Palichisano', 'Pachibelushi'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['uluchelo', 'akasuba'], + + 'year' => 'myaka :count', + 'y' => 'myaka :count', + 'a_year' => 'myaka :count', + + 'month' => 'myeshi :count', + 'm' => 'myeshi :count', + 'a_month' => 'myeshi :count', + + 'week' => 'umulungu :count', + 'w' => 'umulungu :count', + 'a_week' => 'umulungu :count', + + 'day' => 'inshiku :count', + 'd' => 'inshiku :count', + 'a_day' => 'inshiku :count', + + 'hour' => 'awala :count', + 'h' => 'awala :count', + 'a_hour' => 'awala :count', + + 'minute' => 'miniti :count', + 'min' => 'miniti :count', + 'a_minute' => 'miniti :count', + + 'second' => 'sekondi :count', + 's' => 'sekondi :count', + 'a_second' => 'sekondi :count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ber.php b/libraries/Carbon/src/Carbon/Lang/ber.php new file mode 100644 index 00000000000..c16083e2990 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ber.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ber_DZ.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ber_DZ.php b/libraries/Carbon/src/Carbon/Lang/ber_DZ.php new file mode 100644 index 00000000000..b604689f8d5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ber_DZ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'birinci gün', 'ikinci gün', 'üçüncü gün', 'dördüncü gün', 'beşinci gün', 'altıncı gün'], + 'weekdays_short' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'weekdays_min' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ber_MA.php b/libraries/Carbon/src/Carbon/Lang/ber_MA.php new file mode 100644 index 00000000000..b604689f8d5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ber_MA.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'birinci gün', 'ikinci gün', 'üçüncü gün', 'dördüncü gün', 'beşinci gün', 'altıncı gün'], + 'weekdays_short' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'weekdays_min' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bez.php b/libraries/Carbon/src/Carbon/Lang/bez.php new file mode 100644 index 00000000000..322f7e928f4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bez.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['pamilau', 'pamunyi'], + 'weekdays' => ['pa mulungu', 'pa shahuviluha', 'pa hivili', 'pa hidatu', 'pa hitayi', 'pa hihanu', 'pa shahulembela'], + 'weekdays_short' => ['Mul', 'Vil', 'Hiv', 'Hid', 'Hit', 'Hih', 'Lem'], + 'weekdays_min' => ['Mul', 'Vil', 'Hiv', 'Hid', 'Hit', 'Hih', 'Lem'], + 'months' => ['pa mwedzi gwa hutala', 'pa mwedzi gwa wuvili', 'pa mwedzi gwa wudatu', 'pa mwedzi gwa wutai', 'pa mwedzi gwa wuhanu', 'pa mwedzi gwa sita', 'pa mwedzi gwa saba', 'pa mwedzi gwa nane', 'pa mwedzi gwa tisa', 'pa mwedzi gwa kumi', 'pa mwedzi gwa kumi na moja', 'pa mwedzi gwa kumi na mbili'], + 'months_short' => ['Hut', 'Vil', 'Dat', 'Tai', 'Han', 'Sit', 'Sab', 'Nan', 'Tis', 'Kum', 'Kmj', 'Kmb'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bg.php b/libraries/Carbon/src/Carbon/Lang/bg.php new file mode 100644 index 00000000000..3685b9a8ae8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bg.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count година|:count години', + 'a_year' => 'година|:count години', + 'y' => ':count година|:count години', + 'month' => ':count месец|:count месеца', + 'a_month' => 'месец|:count месеца', + 'm' => ':count месец|:count месеца', + 'week' => ':count седмица|:count седмици', + 'a_week' => 'седмица|:count седмици', + 'w' => ':count седмица|:count седмици', + 'day' => ':count ден|:count дни', + 'a_day' => 'ден|:count дни', + 'd' => ':count ден|:count дни', + 'hour' => ':count час|:count часа', + 'a_hour' => 'час|:count часа', + 'h' => ':count час|:count часа', + 'minute' => ':count минута|:count минути', + 'a_minute' => 'минута|:count минути', + 'min' => ':count минута|:count минути', + 'second' => ':count секунда|:count секунди', + 'a_second' => 'няколко секунди|:count секунди', + 's' => ':count секунда|:count секунди', + 'ago' => 'преди :time', + 'from_now' => 'след :time', + 'after' => 'след :time', + 'before' => 'преди :time', + 'diff_now' => 'сега', + 'diff_today' => 'Днес', + 'diff_today_regexp' => 'Днес(?:\\s+в)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера(?:\\s+в)?', + 'diff_tomorrow' => 'утре', + 'diff_tomorrow_regexp' => 'Утре(?:\\s+в)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Днес в] LT', + 'nextDay' => '[Утре в] LT', + 'nextWeek' => 'dddd [в] LT', + 'lastDay' => '[Вчера в] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + case 3: + case 6: + return '[В изминалата] dddd [в] LT'; + default: + return '[В изминалия] dddd [в] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + $last2Digits = $number % 100; + if ($number === 0) { + return "$number-ев"; + } + if ($last2Digits === 0) { + return "$number-ен"; + } + if ($last2Digits > 10 && $last2Digits < 20) { + return "$number-ти"; + } + if ($lastDigit === 1) { + return "$number-ви"; + } + if ($lastDigit === 2) { + return "$number-ри"; + } + if ($lastDigit === 7 || $lastDigit === 8) { + return "$number-ми"; + } + + return "$number-ти"; + }, + 'months' => ['януари', 'февруари', 'март', 'април', 'май', 'юни', 'юли', 'август', 'септември', 'октомври', 'ноември', 'декември'], + 'months_short' => ['яну', 'фев', 'мар', 'апр', 'май', 'юни', 'юли', 'авг', 'сеп', 'окт', 'ное', 'дек'], + 'weekdays' => ['неделя', 'понеделник', 'вторник', 'сряда', 'четвъртък', 'петък', 'събота'], + 'weekdays_short' => ['нед', 'пон', 'вто', 'сря', 'чет', 'пет', 'съб'], + 'weekdays_min' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['преди обяд', 'следобед'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/bg_BG.php b/libraries/Carbon/src/Carbon/Lang/bg_BG.php new file mode 100644 index 00000000000..cb4ff6b8fea --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bg_BG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bg.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/bhb.php b/libraries/Carbon/src/Carbon/Lang/bhb.php new file mode 100644 index 00000000000..e481b8f022e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bhb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bhb_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/bhb_IN.php b/libraries/Carbon/src/Carbon/Lang/bhb_IN.php new file mode 100644 index 00000000000..e00a1599d7c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bhb_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. alexey.merzlyakov@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bho.php b/libraries/Carbon/src/Carbon/Lang/bho.php new file mode 100644 index 00000000000..fb691b16aa5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bho.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bho_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/bho_IN.php b/libraries/Carbon/src/Carbon/Lang/bho_IN.php new file mode 100644 index 00000000000..e2de872f83f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bho_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर"'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर"'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'hour' => ':count मौसम', + 'h' => ':count मौसम', + 'a_hour' => ':count मौसम', + + 'minute' => ':count कला', + 'min' => ':count कला', + 'a_minute' => ':count कला', + + 'second' => ':count सोमार', + 's' => ':count सोमार', + 'a_second' => ':count सोमार', + + 'year' => ':count साल', + 'y' => ':count साल', + 'a_year' => ':count साल', + + 'month' => ':count महिना', + 'm' => ':count महिना', + 'a_month' => ':count महिना', + + 'week' => ':count सप्ताह', + 'w' => ':count सप्ताह', + 'a_week' => ':count सप्ताह', + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bi.php b/libraries/Carbon/src/Carbon/Lang/bi.php new file mode 100644 index 00000000000..82633ba1c9f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bi_VU.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/bi_VU.php b/libraries/Carbon/src/Carbon/Lang/bi_VU.php new file mode 100644 index 00000000000..3d3b23a04f3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bi_VU.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com & maninder1.s@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['jenuware', 'febwari', 'maj', 'epril', 'mei', 'jun', 'julae', 'ogis', 'septemba', 'oktoba', 'novemba', 'disemba'], + 'months_short' => ['jen', 'feb', 'maj', 'epr', 'mei', 'jun', 'jul', 'ogi', 'sep', 'okt', 'nov', 'dis'], + 'weekdays' => ['sande', 'mande', 'maj', 'wota', 'fraede', 'sarede'], + 'weekdays_short' => ['san', 'man', 'maj', 'wot', 'fra', 'sar'], + 'weekdays_min' => ['san', 'man', 'maj', 'wot', 'fra', 'sar'], + + 'year' => ':count seven', // less reliable + 'y' => ':count seven', // less reliable + 'a_year' => ':count seven', // less reliable + + 'month' => ':count mi', // less reliable + 'm' => ':count mi', // less reliable + 'a_month' => ':count mi', // less reliable + + 'week' => ':count sarede', // less reliable + 'w' => ':count sarede', // less reliable + 'a_week' => ':count sarede', // less reliable + + 'day' => ':count betde', // less reliable + 'd' => ':count betde', // less reliable + 'a_day' => ':count betde', // less reliable + + 'hour' => ':count klok', // less reliable + 'h' => ':count klok', // less reliable + 'a_hour' => ':count klok', // less reliable + + 'minute' => ':count smol', // less reliable + 'min' => ':count smol', // less reliable + 'a_minute' => ':count smol', // less reliable + + 'second' => ':count tu', // less reliable + 's' => ':count tu', // less reliable + 'a_second' => ':count tu', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bm.php b/libraries/Carbon/src/Carbon/Lang/bm.php new file mode 100644 index 00000000000..9135ebe5c1e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bm.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Estelle Comment + */ +return [ + 'year' => 'san :count', + 'a_year' => '{1}san kelen|san :count', + 'y' => 'san :count', + 'month' => 'kalo :count', + 'a_month' => '{1}kalo kelen|kalo :count', + 'm' => 'k. :count', + 'week' => 'dɔgɔkun :count', + 'a_week' => 'dɔgɔkun kelen', + 'w' => 'd. :count', + 'day' => 'tile :count', + 'd' => 't. :count', + 'a_day' => '{1}tile kelen|tile :count', + 'hour' => 'lɛrɛ :count', + 'a_hour' => '{1}lɛrɛ kelen|lɛrɛ :count', + 'h' => 'l. :count', + 'minute' => 'miniti :count', + 'a_minute' => '{1}miniti kelen|miniti :count', + 'min' => 'm. :count', + 'second' => 'sekondi :count', + 'a_second' => '{1}sanga dama dama|sekondi :count', + 's' => 'sek. :count', + 'ago' => 'a bɛ :time bɔ', + 'from_now' => ':time kɔnɔ', + 'diff_today' => 'Bi', + 'diff_yesterday' => 'Kunu', + 'diff_yesterday_regexp' => 'Kunu(?:\\s+lɛrɛ)?', + 'diff_tomorrow' => 'Sini', + 'diff_tomorrow_regexp' => 'Sini(?:\\s+lɛrɛ)?', + 'diff_today_regexp' => 'Bi(?:\\s+lɛrɛ)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'MMMM [tile] D [san] YYYY', + 'LLL' => 'MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm', + 'LLLL' => 'dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Bi lɛrɛ] LT', + 'nextDay' => '[Sini lɛrɛ] LT', + 'nextWeek' => 'dddd [don lɛrɛ] LT', + 'lastDay' => '[Kunu lɛrɛ] LT', + 'lastWeek' => 'dddd [tɛmɛnen lɛrɛ] LT', + 'sameElse' => 'L', + ], + 'months' => ['Zanwuyekalo', 'Fewuruyekalo', 'Marisikalo', 'Awirilikalo', 'Mɛkalo', 'Zuwɛnkalo', 'Zuluyekalo', 'Utikalo', 'Sɛtanburukalo', 'ɔkutɔburukalo', 'Nowanburukalo', 'Desanburukalo'], + 'months_short' => ['Zan', 'Few', 'Mar', 'Awi', 'Mɛ', 'Zuw', 'Zul', 'Uti', 'Sɛt', 'ɔku', 'Now', 'Des'], + 'weekdays' => ['Kari', 'Ntɛnɛn', 'Tarata', 'Araba', 'Alamisa', 'Juma', 'Sibiri'], + 'weekdays_short' => ['Kar', 'Ntɛ', 'Tar', 'Ara', 'Ala', 'Jum', 'Sib'], + 'weekdays_min' => ['Ka', 'Nt', 'Ta', 'Ar', 'Al', 'Ju', 'Si'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ni '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/bn.php b/libraries/Carbon/src/Carbon/Lang/bn.php new file mode 100644 index 00000000000..1157aab44c0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bn.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Shakib Hossain + * - Raju + * - Aniruddha Adhikary + * - JD Isaacks + * - Saiful Islam + * - Faisal Islam + */ +return [ + 'year' => ':count বছর', + 'a_year' => 'এক বছর|:count বছর', + 'y' => '১ বছর|:count বছর', + 'month' => ':count মাস', + 'a_month' => 'এক মাস|:count মাস', + 'm' => '১ মাস|:count মাস', + 'week' => ':count সপ্তাহ', + 'a_week' => '১ সপ্তাহ|:count সপ্তাহ', + 'w' => '১ সপ্তাহ|:count সপ্তাহ', + 'day' => ':count দিন', + 'a_day' => 'এক দিন|:count দিন', + 'd' => '১ দিন|:count দিন', + 'hour' => ':count ঘন্টা', + 'a_hour' => 'এক ঘন্টা|:count ঘন্টা', + 'h' => '১ ঘন্টা|:count ঘন্টা', + 'minute' => ':count মিনিট', + 'a_minute' => 'এক মিনিট|:count মিনিট', + 'min' => '১ মিনিট|:count মিনিট', + 'second' => ':count সেকেন্ড', + 'a_second' => 'কয়েক সেকেন্ড|:count সেকেন্ড', + 's' => '১ সেকেন্ড|:count সেকেন্ড', + 'ago' => ':time আগে', + 'from_now' => ':time পরে', + 'after' => ':time পরে', + 'before' => ':time আগে', + 'diff_now' => 'এখন', + 'diff_today' => 'আজ', + 'diff_yesterday' => 'গতকাল', + 'diff_tomorrow' => 'আগামীকাল', + 'period_recurrences' => ':count বার|:count বার', + 'period_interval' => 'প্রতি :interval', + 'period_start_date' => ':date থেকে', + 'period_end_date' => ':date পর্যন্ত', + 'formats' => [ + 'LT' => 'A Oh:Om সময়', + 'LTS' => 'A Oh:Om:Os সময়', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY, A Oh:Om সময়', + 'LLLL' => 'dddd, OD MMMM OY, A Oh:Om সময়', + ], + 'calendar' => [ + 'sameDay' => '[আজ] LT', + 'nextDay' => '[আগামীকাল] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[গতকাল] LT', + 'lastWeek' => '[গত] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'রাত'; + } + if ($hour < 10) { + return 'সকাল'; + } + if ($hour < 17) { + return 'দুপুর'; + } + if ($hour < 20) { + return 'বিকাল'; + } + + return 'রাত'; + }, + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারি', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্র', 'মে', 'জুন', 'জুল', 'আগ', 'সেপ্ট', 'অক্টো', 'নভে', 'ডিসে'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গ', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'list' => [', ', ' এবং '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekdays_standalone' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহষ্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_min_standalone' => ['রঃ', 'সোঃ', 'মঃ', 'বুঃ', 'বৃঃ', 'শুঃ', 'শনি'], + 'months_short_standalone' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'alt_numbers' => ['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/bn_BD.php b/libraries/Carbon/src/Carbon/Lang/bn_BD.php new file mode 100644 index 00000000000..360ddfdaaff --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bn_BD.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ankur Group, Taneem Ahmed, Jamil Ahmed + */ +return array_replace_recursive(require __DIR__.'/bn.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'first_day_of_week' => 5, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bn_IN.php b/libraries/Carbon/src/Carbon/Lang/bn_IN.php new file mode 100644 index 00000000000..2d5e9b42d81 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bn_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/bn.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bo.php b/libraries/Carbon/src/Carbon/Lang/bo.php new file mode 100644 index 00000000000..4a95970e5d3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bo.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}ལོ་གཅིག|]1,Inf[:count ལོ', + 'month' => '{1}ཟླ་བ་གཅིག|]1,Inf[:count ཟླ་བ', + 'week' => ':count བདུན་ཕྲག', + 'day' => '{1}ཉིན་གཅིག|]1,Inf[:count ཉིན་', + 'hour' => '{1}ཆུ་ཚོད་གཅིག|]1,Inf[:count ཆུ་ཚོད', + 'minute' => '{1}སྐར་མ་གཅིག|]1,Inf[:count སྐར་མ', + 'second' => '{1}ལམ་སང|]1,Inf[:count སྐར་ཆ།', + 'ago' => ':time སྔན་ལ', + 'from_now' => ':time ལ་', + 'diff_yesterday' => 'ཁ་སང', + 'diff_today' => 'དི་རིང', + 'diff_tomorrow' => 'སང་ཉིན', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[དི་རིང] LT', + 'nextDay' => '[སང་ཉིན] LT', + 'nextWeek' => '[བདུན་ཕྲག་རྗེས་མ], LT', + 'lastDay' => '[ཁ་སང] LT', + 'lastWeek' => '[བདུན་ཕྲག་མཐའ་མ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'མཚན་མོ'; + } + if ($hour < 10) { + return 'ཞོགས་ཀས'; + } + if ($hour < 17) { + return 'ཉིན་གུང'; + } + if ($hour < 20) { + return 'དགོང་དག'; + } + + return 'མཚན་མོ'; + }, + 'months' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'months_short' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'weekdays' => ['གཟའ་ཉི་མ་', 'གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་པ་', 'གཟའ་ཕུར་བུ', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་པ་'], + 'weekdays_short' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ', 'པ་སངས་', 'སྤེན་པ་'], + 'weekdays_min' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ', 'པ་སངས་', 'སྤེན་པ་'], + 'list' => [', ', ' ཨནད་ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'months_standalone' => ['ཟླ་བ་དང་པོ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་པ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུན་པ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/bo_CN.php b/libraries/Carbon/src/Carbon/Lang/bo_CN.php new file mode 100644 index 00000000000..9dfaf882214 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bo_CN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bo.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/bo_IN.php b/libraries/Carbon/src/Carbon/Lang/bo_IN.php new file mode 100644 index 00000000000..649e2f72214 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bo_IN.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bo.php', [ + 'meridiem' => ['སྔ་དྲོ་', 'ཕྱི་དྲོ་'], + 'weekdays' => ['གཟའ་ཉི་མ་', 'གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་པ་', 'གཟའ་ཕུར་བུ་', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་པ་'], + 'weekdays_short' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ་', 'པ་སངས་', 'སྤེན་པ་'], + 'weekdays_min' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ་', 'པ་སངས་', 'སྤེན་པ་'], + 'months' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'months_short' => ['ཟླ་༡', 'ཟླ་༢', 'ཟླ་༣', 'ཟླ་༤', 'ཟླ་༥', 'ཟླ་༦', 'ཟླ་༧', 'ཟླ་༨', 'ཟླ་༩', 'ཟླ་༡༠', 'ཟླ་༡༡', 'ཟླ་༡༢'], + 'months_standalone' => ['ཟླ་བ་དང་པོ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་པ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུན་པ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], + 'weekend' => [0, 0], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY ལོའི་MMMཚེས་D', + 'LLL' => 'སྤྱི་ལོ་YYYY MMMMའི་ཚེས་D h:mm a', + 'LLLL' => 'YYYY MMMMའི་ཚེས་D, dddd h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/br.php b/libraries/Carbon/src/Carbon/Lang/br.php new file mode 100644 index 00000000000..bb62795e79c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/br.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - JD Isaacks + */ +return [ + 'year' => '{1}:count bloaz|{3,4,5,9}:count bloaz|[0,Inf[:count vloaz', + 'a_year' => '{1}ur bloaz|{3,4,5,9}:count bloaz|[0,Inf[:count vloaz', + 'month' => '{1}:count miz|{2}:count viz|[0,Inf[:count miz', + 'a_month' => '{1}ur miz|{2}:count viz|[0,Inf[:count miz', + 'week' => ':count sizhun', + 'a_week' => '{1}ur sizhun|:count sizhun', + 'day' => '{1}:count devezh|{2}:count zevezh|[0,Inf[:count devezh', + 'a_day' => '{1}un devezh|{2}:count zevezh|[0,Inf[:count devezh', + 'hour' => ':count eur', + 'a_hour' => '{1}un eur|:count eur', + 'minute' => '{1}:count vunutenn|{2}:count vunutenn|[0,Inf[:count munutenn', + 'a_minute' => '{1}ur vunutenn|{2}:count vunutenn|[0,Inf[:count munutenn', + 'second' => ':count eilenn', + 'a_second' => '{1}un nebeud segondennoù|[0,Inf[:count eilenn', + 'ago' => ':time \'zo', + 'from_now' => 'a-benn :time', + 'diff_now' => 'bremañ', + 'diff_today' => 'Hiziv', + 'diff_today_regexp' => 'Hiziv(?:\\s+da)?', + 'diff_yesterday' => 'decʼh', + 'diff_yesterday_regexp' => 'Dec\'h(?:\\s+da)?', + 'diff_tomorrow' => 'warcʼhoazh', + 'diff_tomorrow_regexp' => 'Warc\'hoazh(?:\\s+da)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [a viz] MMMM YYYY', + 'LLL' => 'D [a viz] MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D [a viz] MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hiziv da] LT', + 'nextDay' => '[Warc\'hoazh da] LT', + 'nextWeek' => 'dddd [da] LT', + 'lastDay' => '[Dec\'h da] LT', + 'lastWeek' => 'dddd [paset da] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'añ' : 'vet'); + }, + 'months' => ['Genver', 'C\'hwevrer', 'Meurzh', 'Ebrel', 'Mae', 'Mezheven', 'Gouere', 'Eost', 'Gwengolo', 'Here', 'Du', 'Kerzu'], + 'months_short' => ['Gen', 'C\'hwe', 'Meu', 'Ebr', 'Mae', 'Eve', 'Gou', 'Eos', 'Gwe', 'Her', 'Du', 'Ker'], + 'weekdays' => ['Sul', 'Lun', 'Meurzh', 'Merc\'her', 'Yaou', 'Gwener', 'Sadorn'], + 'weekdays_short' => ['Sul', 'Lun', 'Meu', 'Mer', 'Yao', 'Gwe', 'Sad'], + 'weekdays_min' => ['Su', 'Lu', 'Me', 'Mer', 'Ya', 'Gw', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' hag '], + 'meridiem' => ['A.M.', 'G.M.'], + + 'y' => ':count bl.', + 'd' => ':count d', + 'h' => ':count e', + 'min' => ':count min', + 's' => ':count s', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/br_FR.php b/libraries/Carbon/src/Carbon/Lang/br_FR.php new file mode 100644 index 00000000000..e0d9bd072e2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/br_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/br.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/brx.php b/libraries/Carbon/src/Carbon/Lang/brx.php new file mode 100644 index 00000000000..40873a42c33 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/brx.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/brx_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/brx_IN.php b/libraries/Carbon/src/Carbon/Lang/brx_IN.php new file mode 100644 index 00000000000..076fd105efc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/brx_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['जानुवारी', 'फेब्रुवारी', 'मार्स', 'एफ्रिल', 'मे', 'जुन', 'जुलाइ', 'आगस्थ', 'सेबथेज्ब़र', 'अखथबर', 'नबेज्ब़र', 'दिसेज्ब़र'], + 'months_short' => ['जानुवारी', 'फेब्रुवारी', 'मार्स', 'एप्रिल', 'मे', 'जुन', 'जुलाइ', 'आगस्थ', 'सेबथेज्ब़र', 'अखथबर', 'नबेज्ब़र', 'दिसेज्ब़र'], + 'weekdays' => ['रबिबार', 'सोबार', 'मंगलबार', 'बुदबार', 'बिसथिबार', 'सुखुरबार', 'सुनिबार'], + 'weekdays_short' => ['रबि', 'सम', 'मंगल', 'बुद', 'बिसथि', 'सुखुर', 'सुनि'], + 'weekdays_min' => ['रबि', 'सम', 'मंगल', 'बुद', 'बिसथि', 'सुखुर', 'सुनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['फुं.', 'बेलासे.'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bs.php b/libraries/Carbon/src/Carbon/Lang/bs.php new file mode 100644 index 00000000000..6c636df9966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bs.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bokideckonja + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count godina|:count godine|:count godina', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count sedmice|:count sedmicu|:count sedmica', + 'w' => ':count sedmice|:count sedmicu|:count sedmica', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count dan|:count dana|:count dana', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count minut|:count minuta|:count minuta', + 'second' => ':count sekund|:count sekunda|:count sekundi', + 's' => ':count sekund|:count sekunda|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time ranije', + 'diff_now' => 'sada', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'jučer', + 'diff_yesterday_regexp' => 'jučer(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[jučer u] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + default: + return '[prošli] dddd [u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mart', 'april', 'maj', 'juni', 'juli', 'august', 'septembar', 'oktobar', 'novembar', 'decembar'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], + 'meridiem' => ['prijepodne', 'popodne'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/bs_BA.php b/libraries/Carbon/src/Carbon/Lang/bs_BA.php new file mode 100644 index 00000000000..e398021c011 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bs_BA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bs.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/bs_Cyrl.php b/libraries/Carbon/src/Carbon/Lang/bs_Cyrl.php new file mode 100644 index 00000000000..ae14100793b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bs_Cyrl.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bs.php', [ + 'meridiem' => ['пре подне', 'поподне'], + 'weekdays' => ['недјеља', 'понедјељак', 'уторак', 'сриједа', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед', 'пон', 'уто', 'сри', 'чет', 'пет', 'суб'], + 'weekdays_min' => ['нед', 'пон', 'уто', 'сри', 'чет', 'пет', 'суб'], + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јуни', 'јули', 'аугуст', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан', 'феб', 'мар', 'апр', 'мај', 'јун', 'јул', 'ауг', 'сеп', 'окт', 'нов', 'дец'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.YYYY.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/bs_Latn.php b/libraries/Carbon/src/Carbon/Lang/bs_Latn.php new file mode 100644 index 00000000000..d4ca993ee8b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/bs_Latn.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bs.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/byn.php b/libraries/Carbon/src/Carbon/Lang/byn.php new file mode 100644 index 00000000000..a216df3f066 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/byn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/byn_ER.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/byn_ER.php b/libraries/Carbon/src/Carbon/Lang/byn_ER.php new file mode 100644 index 00000000000..71cb5c8679a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/byn_ER.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ልደትሪ', 'ካብኽብቲ', 'ክብላ', 'ፋጅኺሪ', 'ክቢቅሪ', 'ምኪኤል ትጓ̅ኒሪ', 'ኰርኩ', 'ማርያም ትሪ', 'ያኸኒ መሳቅለሪ', 'መተሉ', 'ምኪኤል መሽወሪ', 'ተሕሳስሪ'], + 'months_short' => ['ልደት', 'ካብኽ', 'ክብላ', 'ፋጅኺ', 'ክቢቅ', 'ም/ት', 'ኰር', 'ማርያ', 'ያኸኒ', 'መተሉ', 'ም/ም', 'ተሕሳ'], + 'weekdays' => ['ሰንበር ቅዳዅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ ወሪ ለብዋ', 'ኣምድ', 'ኣርብ', 'ሰንበር ሽጓዅ'], + 'weekdays_short' => ['ሰ/ቅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ', 'ኣምድ', 'ኣርብ', 'ሰ/ሽ'], + 'weekdays_min' => ['ሰ/ቅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ', 'ኣምድ', 'ኣርብ', 'ሰ/ሽ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ፋዱስ ጃብ', 'ፋዱስ ደምቢ'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ca.php b/libraries/Carbon/src/Carbon/Lang/ca.php new file mode 100644 index 00000000000..7da8f32fd2f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ca.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - mestremuten + * - François B + * - Marc Ordinas i Llopis + * - Pere Orga + * - JD Isaacks + * - Quentí + * - Víctor Díaz + * - Xavi + * - qcardona + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count any|:count anys', + 'a_year' => 'un any|:count anys', + 'y' => ':count any|:count anys', + 'month' => ':count mes|:count mesos', + 'a_month' => 'un mes|:count mesos', + 'm' => ':count mes|:count mesos', + 'week' => ':count setmana|:count setmanes', + 'a_week' => 'una setmana|:count setmanes', + 'w' => ':count setmana|:count setmanes', + 'day' => ':count dia|:count dies', + 'a_day' => 'un dia|:count dies', + 'd' => ':count d', + 'hour' => ':count hora|:count hores', + 'a_hour' => 'una hora|:count hores', + 'h' => ':count h', + 'minute' => ':count minut|:count minuts', + 'a_minute' => 'un minut|:count minuts', + 'min' => ':count min', + 'second' => ':count segon|:count segons', + 'a_second' => 'uns segons|:count segons', + 's' => ':count s', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquí a :time', + 'after' => ':time després', + 'before' => ':time abans', + 'diff_now' => 'ara mateix', + 'diff_today' => 'avui', + 'diff_today_regexp' => 'avui(?:\\s+a)?(?:\\s+les)?', + 'diff_yesterday' => 'ahir', + 'diff_yesterday_regexp' => 'ahir(?:\\s+a)?(?:\\s+les)?', + 'diff_tomorrow' => 'demà', + 'diff_tomorrow_regexp' => 'demà(?:\\s+a)?(?:\\s+les)?', + 'diff_before_yesterday' => 'abans d\'ahir', + 'diff_after_tomorrow' => 'demà passat', + 'period_recurrences' => ':count cop|:count cops', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'fins a :date', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [de] YYYY', + 'LLL' => 'D MMMM [de] YYYY [a les] H:mm', + 'LLLL' => 'dddd D MMMM [de] YYYY [a les] H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[avui a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[demà a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd [a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[ahir a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[el] dddd [passat a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + return $number.( + ($period === 'w' || $period === 'W') ? 'a' : ( + ($number === 1) ? 'r' : ( + ($number === 2) ? 'n' : ( + ($number === 3) ? 'r' : ( + ($number === 4) ? 't' : 'è' + ) + ) + ) + ) + ); + }, + 'months' => ['de gener', 'de febrer', 'de març', 'd\'abril', 'de maig', 'de juny', 'de juliol', 'd\'agost', 'de setembre', 'd\'octubre', 'de novembre', 'de desembre'], + 'months_standalone' => ['gener', 'febrer', 'març', 'abril', 'maig', 'juny', 'juliol', 'agost', 'setembre', 'octubre', 'novembre', 'desembre'], + 'months_short' => ['de gen.', 'de febr.', 'de març', 'd\'abr.', 'de maig', 'de juny', 'de jul.', 'd\'ag.', 'de set.', 'd\'oct.', 'de nov.', 'de des.'], + 'months_short_standalone' => ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], + 'months_regexp' => '/(D[oD]?[\s,]+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['diumenge', 'dilluns', 'dimarts', 'dimecres', 'dijous', 'divendres', 'dissabte'], + 'weekdays_short' => ['dg.', 'dl.', 'dt.', 'dc.', 'dj.', 'dv.', 'ds.'], + 'weekdays_min' => ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' i '], + 'meridiem' => ['a. m.', 'p. m.'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ca_AD.php b/libraries/Carbon/src/Carbon/Lang/ca_AD.php new file mode 100644 index 00000000000..d2a28a72a57 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ca_AD.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ca_ES.php b/libraries/Carbon/src/Carbon/Lang/ca_ES.php new file mode 100644 index 00000000000..e2224307741 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ca_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ca.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ca_ES_Valencia.php b/libraries/Carbon/src/Carbon/Lang/ca_ES_Valencia.php new file mode 100644 index 00000000000..5cd1ccdfd6c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ca_ES_Valencia.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'ca'); + }, 'ca_ES_Valencia'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ca_FR.php b/libraries/Carbon/src/Carbon/Lang/ca_FR.php new file mode 100644 index 00000000000..d2a28a72a57 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ca_FR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ca_IT.php b/libraries/Carbon/src/Carbon/Lang/ca_IT.php new file mode 100644 index 00000000000..d2a28a72a57 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ca_IT.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ccp.php b/libraries/Carbon/src/Carbon/Lang/ccp.php new file mode 100644 index 00000000000..bf00401ac21 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ccp.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['𑄢𑄧𑄝𑄨𑄝𑄢𑄴', '𑄥𑄧𑄟𑄴𑄝𑄢𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴𑄝𑄢𑄴', '𑄝𑄪𑄖𑄴𑄝𑄢𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴𑄝𑄢𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴𑄝𑄢𑄴', '𑄥𑄧𑄚𑄨𑄝𑄢𑄴'], + 'weekdays_short' => ['𑄢𑄧𑄝𑄨', '𑄥𑄧𑄟𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴', '𑄝𑄪𑄖𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴', '𑄥𑄧𑄚𑄨'], + 'weekdays_min' => ['𑄢𑄧𑄝𑄨', '𑄥𑄧𑄟𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴', '𑄝𑄪𑄖𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴', '𑄥𑄧𑄚𑄨'], + 'months' => ['𑄎𑄚𑄪𑄠𑄢𑄨', '𑄜𑄬𑄛𑄴𑄝𑄳𑄢𑄪𑄠𑄢𑄨', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄬𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄧𑄢𑄴'], + 'months_short' => ['𑄎𑄚𑄪', '𑄜𑄬𑄛𑄴', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄮𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄢𑄴'], + 'months_short_standalone' => ['𑄎𑄚𑄪𑄠𑄢𑄨', '𑄜𑄬𑄛𑄴𑄝𑄳𑄢𑄪𑄠𑄢𑄨', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄮𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄧𑄢𑄴'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM, YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ccp_IN.php b/libraries/Carbon/src/Carbon/Lang/ccp_IN.php new file mode 100644 index 00000000000..6d8ab8b6608 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ccp_IN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ccp.php', [ + 'weekend' => [0, 0], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ce.php b/libraries/Carbon/src/Carbon/Lang/ce.php new file mode 100644 index 00000000000..f91d103af77 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ce.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ce_RU.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ce_RU.php b/libraries/Carbon/src/Carbon/Lang/ce_RU.php new file mode 100644 index 00000000000..3dd8a8722b6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ce_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANCHR + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.DD.MM', + ], + 'months' => ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['КӀиранан де', 'Оршотан де', 'Шинарин де', 'Кхаарин де', 'Еарин де', 'ПӀераскан де', 'Шот де'], + 'weekdays_short' => ['КӀ', 'Ор', 'Ши', 'Кх', 'Еа', 'ПӀ', 'Шо'], + 'weekdays_min' => ['КӀ', 'Ор', 'Ши', 'Кх', 'Еа', 'ПӀ', 'Шо'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count шо', + 'y' => ':count шо', + 'a_year' => ':count шо', + + 'month' => ':count бутт', + 'm' => ':count бутт', + 'a_month' => ':count бутт', + + 'week' => ':count кӏира', + 'w' => ':count кӏира', + 'a_week' => ':count кӏира', + + 'day' => ':count де', + 'd' => ':count де', + 'a_day' => ':count де', + + 'hour' => ':count сахьт', + 'h' => ':count сахьт', + 'a_hour' => ':count сахьт', + + 'minute' => ':count минот', + 'min' => ':count минот', + 'a_minute' => ':count минот', + + 'second' => ':count секунд', + 's' => ':count секунд', + 'a_second' => ':count секунд', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/cgg.php b/libraries/Carbon/src/Carbon/Lang/cgg.php new file mode 100644 index 00000000000..264c2f1a1a1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cgg.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sande', 'Orwokubanza', 'Orwakabiri', 'Orwakashatu', 'Orwakana', 'Orwakataano', 'Orwamukaaga'], + 'weekdays_short' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'weekdays_min' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'months' => ['Okwokubanza', 'Okwakabiri', 'Okwakashatu', 'Okwakana', 'Okwakataana', 'Okwamukaaga', 'Okwamushanju', 'Okwamunaana', 'Okwamwenda', 'Okwaikumi', 'Okwaikumi na kumwe', 'Okwaikumi na ibiri'], + 'months_short' => ['KBZ', 'KBR', 'KST', 'KKN', 'KTN', 'KMK', 'KMS', 'KMN', 'KMW', 'KKM', 'KNK', 'KNB'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'day' => ':count ruhanga', // less reliable + 'd' => ':count ruhanga', // less reliable + 'a_day' => ':count ruhanga', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/chr.php b/libraries/Carbon/src/Carbon/Lang/chr.php new file mode 100644 index 00000000000..3032e3779a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/chr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/chr_US.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/chr_US.php b/libraries/Carbon/src/Carbon/Lang/chr_US.php new file mode 100644 index 00000000000..66c819933db --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/chr_US.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cherokee Nation Joseph Erb josepherb7@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YYYY', + ], + 'months' => ['ᎤᏃᎸᏔᏅ', 'ᎧᎦᎵ', 'ᎠᏅᏱ', 'ᎧᏬᏂ', 'ᎠᏂᏍᎬᏘ', 'ᏕᎭᎷᏱ', 'ᎫᏰᏉᏂ', 'ᎦᎶᏂ', 'ᏚᎵᏍᏗ', 'ᏚᏂᏅᏗ', 'ᏅᏓᏕᏆ', 'ᎥᏍᎩᏱ'], + 'months_short' => ['ᎤᏃ', 'ᎧᎦ', 'ᎠᏅ', 'ᎧᏬ', 'ᎠᏂ', 'ᏕᎭ', 'ᎫᏰ', 'ᎦᎶ', 'ᏚᎵ', 'ᏚᏂ', 'ᏅᏓ', 'ᎥᏍ'], + 'weekdays' => ['ᎤᎾᏙᏓᏆᏍᎬ', 'ᎤᎾᏙᏓᏉᏅᎯ', 'ᏔᎵᏁᎢᎦ', 'ᏦᎢᏁᎢᎦ', 'ᏅᎩᏁᎢᎦ', 'ᏧᎾᎩᎶᏍᏗ', 'ᎤᎾᏙᏓᏈᏕᎾ'], + 'weekdays_short' => ['ᏆᏍᎬ', 'ᏉᏅᎯ', 'ᏔᎵᏁ', 'ᏦᎢᏁ', 'ᏅᎩᏁ', 'ᏧᎾᎩ', 'ᏈᏕᎾ'], + 'weekdays_min' => ['ᏆᏍᎬ', 'ᏉᏅᎯ', 'ᏔᎵᏁ', 'ᏦᎢᏁ', 'ᏅᎩᏁ', 'ᏧᎾᎩ', 'ᏈᏕᎾ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ᏌᎾᎴ', 'ᏒᎯᏱᎢᏗᏢ', 'ꮜꮎꮄ', 'ꮢꭿᏹꭲꮧꮲ'], + + 'second' => ':count ᏐᎢ', // less reliable + 's' => ':count ᏐᎢ', // less reliable + 'a_second' => ':count ᏐᎢ', // less reliable + + 'year' => ':count ᏑᏕᏘᏴᏓ', + 'y' => ':count ᏑᏕᏘᏴᏓ', + 'a_year' => ':count ᏑᏕᏘᏴᏓ', + + 'month' => ':count ᏏᏅᏙ', + 'm' => ':count ᏏᏅᏙ', + 'a_month' => ':count ᏏᏅᏙ', + + 'week' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + 'w' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + 'a_week' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + + 'day' => ':count ᎢᎦ', + 'd' => ':count ᎢᎦ', + 'a_day' => ':count ᎢᎦ', + + 'hour' => ':count ᏑᏟᎶᏛ', + 'h' => ':count ᏑᏟᎶᏛ', + 'a_hour' => ':count ᏑᏟᎶᏛ', + + 'minute' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + 'min' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + 'a_minute' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + + 'ago' => ':time ᏥᎨᏒ', + 'from_now' => 'ᎾᎿ :time', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ckb.php b/libraries/Carbon/src/Carbon/Lang/ckb.php new file mode 100644 index 00000000000..d448ac52f15 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ckb.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Swara Mohammed + */ +$months = [ + 'ڕێبەندان', + 'ڕەشەمە', + 'نەورۆز', + 'گوڵان', + 'جۆزەردان', + 'پوشپەڕ', + 'گەلاوێژ', + 'خەرمانان', + 'ڕەزبەر', + 'گەڵاڕێزان', + 'سەرماوەرز', + 'بەفرانبار', +]; + +return [ + 'year' => implode('|', ['{0}:count ساڵێک', '{1}ساڵێک', '{2}دوو ساڵ', ']2,11[:count ساڵ', ']10,Inf[:count ساڵ']), + 'a_year' => implode('|', ['{0}:count ساڵێک', '{1}ساڵێک', '{2}دوو ساڵ', ']2,11[:count ساڵ', ']10,Inf[:count ساڵ']), + 'month' => implode('|', ['{0}:count مانگێک', '{1}مانگێک', '{2}دوو مانگ', ']2,11[:count مانگ', ']10,Inf[:count مانگ']), + 'a_month' => implode('|', ['{0}:count مانگێک', '{1}مانگێک', '{2}دوو مانگ', ']2,11[:count مانگ', ']10,Inf[:count مانگ']), + 'week' => implode('|', ['{0}:count هەفتەیەک', '{1}هەفتەیەک', '{2}دوو هەفتە', ']2,11[:count هەفتە', ']10,Inf[:count هەفتە']), + 'a_week' => implode('|', ['{0}:count هەفتەیەک', '{1}هەفتەیەک', '{2}دوو هەفتە', ']2,11[:count هەفتە', ']10,Inf[:count هەفتە']), + 'day' => implode('|', ['{0}:count ڕۆژێک', '{1}ڕۆژێک', '{2}دوو ڕۆژ', ']2,11[:count ڕۆژ', ']10,Inf[:count ڕۆژ']), + 'a_day' => implode('|', ['{0}:count ڕۆژێک', '{1}ڕۆژێک', '{2}دوو ڕۆژ', ']2,11[:count ڕۆژ', ']10,Inf[:count ڕۆژ']), + 'hour' => implode('|', ['{0}:count کاتژمێرێک', '{1}کاتژمێرێک', '{2}دوو کاتژمێر', ']2,11[:count کاتژمێر', ']10,Inf[:count کاتژمێر']), + 'a_hour' => implode('|', ['{0}:count کاتژمێرێک', '{1}کاتژمێرێک', '{2}دوو کاتژمێر', ']2,11[:count کاتژمێر', ']10,Inf[:count کاتژمێر']), + 'minute' => implode('|', ['{0}:count خولەکێک', '{1}خولەکێک', '{2}دوو خولەک', ']2,11[:count خولەک', ']10,Inf[:count خولەک']), + 'a_minute' => implode('|', ['{0}:count خولەکێک', '{1}خولەکێک', '{2}دوو خولەک', ']2,11[:count خولەک', ']10,Inf[:count خولەک']), + 'second' => implode('|', ['{0}:count چرکەیەک', '{1}چرکەیەک', '{2}دوو چرکە', ']2,11[:count چرکە', ']10,Inf[:count چرکە']), + 'a_second' => implode('|', ['{0}:count چرکەیەک', '{1}چرکەیەک', '{2}دوو چرکە', ']2,11[:count چرکە', ']10,Inf[:count چرکە']), + 'ago' => 'پێش :time', + 'from_now' => ':time لە ئێستاوە', + 'after' => 'دوای :time', + 'before' => 'پێش :time', + 'diff_now' => 'ئێستا', + 'diff_today' => 'ئەمڕۆ', + 'diff_today_regexp' => 'ڕۆژ(?:\\s+لە)?(?:\\s+کاتژمێر)?', + 'diff_yesterday' => 'دوێنێ', + 'diff_yesterday_regexp' => 'دوێنێ(?:\\s+لە)?(?:\\s+کاتژمێر)?', + 'diff_tomorrow' => 'سبەینێ', + 'diff_tomorrow_regexp' => 'سبەینێ(?:\\s+لە)?(?:\\s+کاتژمێر)?', + 'diff_before_yesterday' => 'پێش دوێنێ', + 'diff_after_tomorrow' => 'دوای سبەینێ', + 'period_recurrences' => implode('|', ['{0}جار', '{1}جار', '{2}:count دووجار', ']2,11[:count جار', ']10,Inf[:count جار']), + 'period_interval' => 'هەموو :interval', + 'period_start_date' => 'لە :date', + 'period_end_date' => 'بۆ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['یەکشەممە', 'دووشەممە', 'سێشەممە', 'چوارشەممە', 'پێنجشەممە', 'هەینی', 'شەممە'], + 'weekdays_short' => ['یەکشەممە', 'دووشەممە', 'سێشەممە', 'چوارشەممە', 'پێنجشەممە', 'هەینی', 'شەممە'], + 'weekdays_min' => ['یەکشەممە', 'دووشەممە', 'سێشەممە', 'چوارشەممە', 'پێنجشەممە', 'هەینی', 'شەممە'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ئەمڕۆ لە کاتژمێر] LT', + 'nextDay' => '[سبەینێ لە کاتژمێر] LT', + 'nextWeek' => 'dddd [لە کاتژمێر] LT', + 'lastDay' => '[دوێنێ لە کاتژمێر] LT', + 'lastWeek' => 'dddd [لە کاتژمێر] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['پ.ن', 'د.ن'], + 'weekend' => [5, 6], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/cmn.php b/libraries/Carbon/src/Carbon/Lang/cmn.php new file mode 100644 index 00000000000..bc73425bb26 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cmn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/cmn_TW.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/cmn_TW.php b/libraries/Carbon/src/Carbon/Lang/cmn_TW.php new file mode 100644 index 00000000000..ae4114aefa9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cmn_TW.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD號', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'meridiem' => ['上午', '下午'], + + 'year' => ':count 年', + 'y' => ':count 年', + 'a_year' => ':count 年', + + 'month' => ':count 月', + 'm' => ':count 月', + 'a_month' => ':count 月', + + 'week' => ':count 周', + 'w' => ':count 周', + 'a_week' => ':count 周', + + 'day' => ':count 白天', + 'd' => ':count 白天', + 'a_day' => ':count 白天', + + 'hour' => ':count 小时', + 'h' => ':count 小时', + 'a_hour' => ':count 小时', + + 'minute' => ':count 分钟', + 'min' => ':count 分钟', + 'a_minute' => ':count 分钟', + + 'second' => ':count 秒', + 's' => ':count 秒', + 'a_second' => ':count 秒', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/crh.php b/libraries/Carbon/src/Carbon/Lang/crh.php new file mode 100644 index 00000000000..a71352d1c46 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/crh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/crh_UA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/crh_UA.php b/libraries/Carbon/src/Carbon/Lang/crh_UA.php new file mode 100644 index 00000000000..4c80086f828 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/crh_UA.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Reşat SABIQ tilde.birlik@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'Mayıs', 'İyun', 'İyul', 'Avgust', 'Sentâbr', 'Oktâbr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Bazar', 'Bazarertesi', 'Salı', 'Çarşembe', 'Cumaaqşamı', 'Cuma', 'Cumaertesi'], + 'weekdays_short' => ['Baz', 'Ber', 'Sal', 'Çar', 'Caq', 'Cum', 'Cer'], + 'weekdays_min' => ['Baz', 'Ber', 'Sal', 'Çar', 'Caq', 'Cum', 'Cer'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ÜE', 'ÜS'], + + 'year' => ':count yıl', + 'y' => ':count yıl', + 'a_year' => ':count yıl', + + 'month' => ':count ay', + 'm' => ':count ay', + 'a_month' => ':count ay', + + 'week' => ':count afta', + 'w' => ':count afta', + 'a_week' => ':count afta', + + 'day' => ':count kün', + 'd' => ':count kün', + 'a_day' => ':count kün', + + 'hour' => ':count saat', + 'h' => ':count saat', + 'a_hour' => ':count saat', + + 'minute' => ':count daqqa', + 'min' => ':count daqqa', + 'a_minute' => ':count daqqa', + + 'second' => ':count ekinci', + 's' => ':count ekinci', + 'a_second' => ':count ekinci', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/cs.php b/libraries/Carbon/src/Carbon/Lang/cs.php new file mode 100644 index 00000000000..0c70577f164 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cs.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Jakub Tesinsky + * - Martin Suja + * - Nikos Timiopulos + * - Bohuslav Blín + * - Tsutomu Kuroda + * - tjku + * - Lukas Svoboda + * - Max Melentiev + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Václav Pávek + * - CodeSkills + * - Tlapi + * - newman101 + * - Petr Kadlec + * - tommaskraus + * - Karel Sommer (calvera) + */ +$za = function ($time) { + return 'za '.strtr($time, [ + 'hodina' => 'hodinu', + 'minuta' => 'minutu', + 'sekunda' => 'sekundu', + ]); +}; + +$pred = function ($time) { + $time = strtr($time, [ + 'hodina' => 'hodinou', + 'minuta' => 'minutou', + 'sekunda' => 'sekundou', + ]); + $time = preg_replace('/hodiny?(?!\w)/', 'hodinami', $time); + $time = preg_replace('/minuty?(?!\w)/', 'minutami', $time); + $time = preg_replace('/sekundy?(?!\w)/', 'sekundami', $time); + + return "před $time"; +}; + +return [ + 'year' => ':count rok|:count roky|:count let', + 'y' => ':count rok|:count roky|:count let', + 'a_year' => 'rok|:count roky|:count let', + 'month' => ':count měsíc|:count měsíce|:count měsíců', + 'm' => ':count měs.', + 'a_month' => 'měsíc|:count měsíce|:count měsíců', + 'week' => ':count týden|:count týdny|:count týdnů', + 'w' => ':count týd.', + 'a_week' => 'týden|:count týdny|:count týdnů', + 'day' => ':count den|:count dny|:count dní', + 'd' => ':count den|:count dny|:count dní', + 'a_day' => 'den|:count dny|:count dní', + 'hour' => ':count hodina|:count hodiny|:count hodin', + 'h' => ':count hod.', + 'a_hour' => 'hodina|:count hodiny|:count hodin', + 'minute' => ':count minuta|:count minuty|:count minut', + 'min' => ':count min.', + 'a_minute' => 'minuta|:count minuty|:count minut', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 's' => ':count sek.', + 'a_second' => 'pár sekund|:count sekundy|:count sekund', + + 'month_ago' => ':count měsícem|:count měsíci|:count měsíci', + 'a_month_ago' => 'měsícem|:count měsíci|:count měsíci', + 'day_ago' => ':count dnem|:count dny|:count dny', + 'a_day_ago' => 'dnem|:count dny|:count dny', + 'week_ago' => ':count týdnem|:count týdny|:count týdny', + 'a_week_ago' => 'týdnem|:count týdny|:count týdny', + 'year_ago' => ':count rokem|:count roky|:count lety', + 'y_ago' => ':count rok.|:count rok.|:count let.', + 'a_year_ago' => 'rokem|:count roky|:count lety', + + 'month_before' => ':count měsícem|:count měsíci|:count měsíci', + 'a_month_before' => 'měsícem|:count měsíci|:count měsíci', + 'day_before' => ':count dnem|:count dny|:count dny', + 'a_day_before' => 'dnem|:count dny|:count dny', + 'week_before' => ':count týdnem|:count týdny|:count týdny', + 'a_week_before' => 'týdnem|:count týdny|:count týdny', + 'year_before' => ':count rokem|:count roky|:count lety', + 'y_before' => ':count rok.|:count rok.|:count let.', + 'a_year_before' => 'rokem|:count roky|:count lety', + + 'ago' => $pred, + 'from_now' => $za, + 'before' => $pred, + 'after' => $za, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'months' => ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'], + 'months_standalone' => ['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec'], + 'months_short' => ['led', 'úno', 'bře', 'dub', 'kvě', 'čvn', 'čvc', 'srp', 'zář', 'říj', 'lis', 'pro'], + 'weekdays' => ['neděle', 'pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'úte', 'stř', 'čtv', 'pát', 'sob'], + 'weekdays_min' => ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'], + 'list' => [', ', ' a '], + 'diff_now' => 'nyní', + 'diff_yesterday' => 'včera', + 'diff_tomorrow' => 'zítra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD. MM. YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY HH:mm', + ], + 'meridiem' => ['dopoledne', 'odpoledne'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/cs_CZ.php b/libraries/Carbon/src/Carbon/Lang/cs_CZ.php new file mode 100644 index 00000000000..40ebedfc41b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cs_CZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cs.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/csb.php b/libraries/Carbon/src/Carbon/Lang/csb.php new file mode 100644 index 00000000000..18d43cc8371 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/csb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/csb_PL.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/csb_PL.php b/libraries/Carbon/src/Carbon/Lang/csb_PL.php new file mode 100644 index 00000000000..3297d8cd161 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/csb_PL.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - csb_PL locale Michal Ostrowski bug-glibc-locales@gnu.org + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'months' => ['stëcznika', 'gromicznika', 'strëmiannika', 'łżëkwiata', 'maja', 'czerwińca', 'lëpińca', 'zélnika', 'séwnika', 'rujana', 'lëstopadnika', 'gòdnika'], + 'months_short' => ['stë', 'gro', 'str', 'łżë', 'maj', 'cze', 'lëp', 'zél', 'séw', 'ruj', 'lës', 'gòd'], + 'weekdays' => ['niedzela', 'pòniedzôłk', 'wtórk', 'strzoda', 'czwiôrtk', 'piątk', 'sobòta'], + 'weekdays_short' => ['nie', 'pòn', 'wtó', 'str', 'czw', 'pią', 'sob'], + 'weekdays_min' => ['nie', 'pòn', 'wtó', 'str', 'czw', 'pią', 'sob'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a téż '], + 'two_words_connector' => ' a téż ', + 'year' => ':count rok', + 'month' => ':count miesiąc', + 'week' => ':count tidzéń', + 'day' => ':count dzéń', + 'hour' => ':count gòdzëna', + 'minute' => ':count minuta', + 'second' => ':count sekunda', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/cu.php b/libraries/Carbon/src/Carbon/Lang/cu.php new file mode 100644 index 00000000000..ea7c31c9029 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cu.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count лѣто', + 'y' => ':count лѣто', + 'a_year' => ':count лѣто', + + 'month' => ':count мѣсѧць', + 'm' => ':count мѣсѧць', + 'a_month' => ':count мѣсѧць', + + 'week' => ':count сєдмица', + 'w' => ':count сєдмица', + 'a_week' => ':count сєдмица', + + 'day' => ':count дьнь', + 'd' => ':count дьнь', + 'a_day' => ':count дьнь', + + 'hour' => ':count година', + 'h' => ':count година', + 'a_hour' => ':count година', + + 'minute' => ':count малъ', // less reliable + 'min' => ':count малъ', // less reliable + 'a_minute' => ':count малъ', // less reliable + + 'second' => ':count въторъ', + 's' => ':count въторъ', + 'a_second' => ':count въторъ', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/cv.php b/libraries/Carbon/src/Carbon/Lang/cv.php new file mode 100644 index 00000000000..3044371682b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cv.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + */ +return [ + 'year' => ':count ҫул', + 'a_year' => '{1}пӗр ҫул|:count ҫул', + 'month' => ':count уйӑх', + 'a_month' => '{1}пӗр уйӑх|:count уйӑх', + 'week' => ':count эрне', + 'a_week' => '{1}пӗр эрне|:count эрне', + 'day' => ':count кун', + 'a_day' => '{1}пӗр кун|:count кун', + 'hour' => ':count сехет', + 'a_hour' => '{1}пӗр сехет|:count сехет', + 'minute' => ':count минут', + 'a_minute' => '{1}пӗр минут|:count минут', + 'second' => ':count ҫеккунт', + 'a_second' => '{1}пӗр-ик ҫеккунт|:count ҫеккунт', + 'ago' => ':time каялла', + 'from_now' => function ($time) { + return $time.(preg_match('/сехет$/u', $time) ? 'рен' : (preg_match('/ҫул/u', $time) ? 'тан' : 'ран')); + }, + 'diff_yesterday' => 'Ӗнер', + 'diff_today' => 'Паян', + 'diff_tomorrow' => 'Ыран', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ]', + 'LLL' => 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm', + 'LLLL' => 'dddd, YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Паян] LT [сехетре]', + 'nextDay' => '[Ыран] LT [сехетре]', + 'nextWeek' => '[Ҫитес] dddd LT [сехетре]', + 'lastDay' => '[Ӗнер] LT [сехетре]', + 'lastWeek' => '[Иртнӗ] dddd LT [сехетре]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number-мӗш', + 'months' => ['кӑрлач', 'нарӑс', 'пуш', 'ака', 'май', 'ҫӗртме', 'утӑ', 'ҫурла', 'авӑн', 'юпа', 'чӳк', 'раштав'], + 'months_short' => ['кӑр', 'нар', 'пуш', 'ака', 'май', 'ҫӗр', 'утӑ', 'ҫур', 'авн', 'юпа', 'чӳк', 'раш'], + 'weekdays' => ['вырсарникун', 'тунтикун', 'ытларикун', 'юнкун', 'кӗҫнерникун', 'эрнекун', 'шӑматкун'], + 'weekdays_short' => ['выр', 'тун', 'ытл', 'юн', 'кӗҫ', 'эрн', 'шӑм'], + 'weekdays_min' => ['вр', 'тн', 'ыт', 'юн', 'кҫ', 'эр', 'шм'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' тата '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/cv_RU.php b/libraries/Carbon/src/Carbon/Lang/cv_RU.php new file mode 100644 index 00000000000..6ef24cb8de8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cv_RU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cv.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/cy.php b/libraries/Carbon/src/Carbon/Lang/cy.php new file mode 100644 index 00000000000..d1da48cbf5b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cy.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Daniel Monaghan + */ +return [ + 'year' => '{1}blwyddyn|]1,Inf[:count flynedd', + 'y' => ':countbl', + 'month' => '{1}mis|]1,Inf[:count mis', + 'm' => ':countmi', + 'week' => ':count wythnos', + 'w' => ':countw', + 'day' => '{1}diwrnod|]1,Inf[:count diwrnod', + 'd' => ':countd', + 'hour' => '{1}awr|]1,Inf[:count awr', + 'h' => ':counth', + 'minute' => '{1}munud|]1,Inf[:count munud', + 'min' => ':countm', + 'second' => '{1}ychydig eiliadau|]1,Inf[:count eiliad', + 's' => ':counts', + 'ago' => ':time yn ôl', + 'from_now' => 'mewn :time', + 'after' => ':time ar ôl', + 'before' => ':time o\'r blaen', + 'diff_now' => 'nawr', + 'diff_today' => 'Heddiw', + 'diff_today_regexp' => 'Heddiw(?:\\s+am)?', + 'diff_yesterday' => 'ddoe', + 'diff_yesterday_regexp' => 'Ddoe(?:\\s+am)?', + 'diff_tomorrow' => 'yfory', + 'diff_tomorrow_regexp' => 'Yfory(?:\\s+am)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Heddiw am] LT', + 'nextDay' => '[Yfory am] LT', + 'nextWeek' => 'dddd [am] LT', + 'lastDay' => '[Ddoe am] LT', + 'lastWeek' => 'dddd [diwethaf am] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.( + $number > 20 + ? (\in_array((int) $number, [40, 50, 60, 80, 100], true) ? 'fed' : 'ain') + : ([ + '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed + 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed', // 11eg to 20fed + ])[$number] ?? '' + ); + }, + 'months' => ['Ionawr', 'Chwefror', 'Mawrth', 'Ebrill', 'Mai', 'Mehefin', 'Gorffennaf', 'Awst', 'Medi', 'Hydref', 'Tachwedd', 'Rhagfyr'], + 'months_short' => ['Ion', 'Chwe', 'Maw', 'Ebr', 'Mai', 'Meh', 'Gor', 'Aws', 'Med', 'Hyd', 'Tach', 'Rhag'], + 'weekdays' => ['Dydd Sul', 'Dydd Llun', 'Dydd Mawrth', 'Dydd Mercher', 'Dydd Iau', 'Dydd Gwener', 'Dydd Sadwrn'], + 'weekdays_short' => ['Sul', 'Llun', 'Maw', 'Mer', 'Iau', 'Gwe', 'Sad'], + 'weekdays_min' => ['Su', 'Ll', 'Ma', 'Me', 'Ia', 'Gw', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a '], + 'meridiem' => ['yb', 'yh'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/cy_GB.php b/libraries/Carbon/src/Carbon/Lang/cy_GB.php new file mode 100644 index 00000000000..79a525f48fa --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/cy_GB.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cy.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/da.php b/libraries/Carbon/src/Carbon/Lang/da.php new file mode 100644 index 00000000000..184bf41d89d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/da.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rune Mønnike + * - François B + * - codenhagen + * - JD Isaacks + * - Jens Herlevsen + * - Ulrik McArdle (mcardle) + * - Frederik Sauer (FrittenKeeZ) + * - Janus Bahs Jacquet (kokoshneta) + */ +return [ + 'year' => ':count år|:count år', + 'a_year' => 'et år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count måned|:count måneder', + 'a_month' => 'en måned|:count måneder', + 'm' => ':count mdr.', + 'week' => ':count uge|:count uger', + 'a_week' => 'en uge|:count uger', + 'w' => ':count u.', + 'day' => ':count dag|:count dage', + 'a_day' => ':count dag|:count dage', + 'd' => ':count d.', + 'hour' => ':count time|:count timer', + 'a_hour' => 'en time|:count timer', + 'h' => ':count t.', + 'minute' => ':count minut|:count minutter', + 'a_minute' => 'et minut|:count minutter', + 'min' => ':count min.', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'få sekunder|:count sekunder', + 's' => ':count s.', + 'ago' => 'for :time siden', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time før', + 'diff_now' => 'nu', + 'diff_today' => 'i dag', + 'diff_today_regexp' => 'i dag(?:\\s+kl.)?', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'i går(?:\\s+kl.)?', + 'diff_tomorrow' => 'i morgen', + 'diff_tomorrow_regexp' => 'i morgen(?:\\s+kl.)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd [d.] D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i dag kl.] LT', + 'nextDay' => '[i morgen kl.] LT', + 'nextWeek' => 'på dddd [kl.] LT', + 'lastDay' => '[i går kl.] LT', + 'lastWeek' => '[i] dddd[s kl.] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'], + 'weekdays_short' => ['søn.', 'man.', 'tir.', 'ons.', 'tor.', 'fre.', 'lør.'], + 'weekdays_min' => ['sø', 'ma', 'ti', 'on', 'to', 'fr', 'lø'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/da_DK.php b/libraries/Carbon/src/Carbon/Lang/da_DK.php new file mode 100644 index 00000000000..610cd36d0db --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/da_DK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/da.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/da_GL.php b/libraries/Carbon/src/Carbon/Lang/da_GL.php new file mode 100644 index 00000000000..07c92d4ebac --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/da_GL.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/da.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D. MMM YYYY', + 'LLL' => 'D. MMMM YYYY HH.mm', + 'LLLL' => 'dddd [den] D. MMMM YYYY HH.mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/dav.php b/libraries/Carbon/src/Carbon/Lang/dav.php new file mode 100644 index 00000000000..4b4a7548bbf --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dav.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Luma lwa K', 'luma lwa p'], + 'weekdays' => ['Ituku ja jumwa', 'Kuramuka jimweri', 'Kuramuka kawi', 'Kuramuka kadadu', 'Kuramuka kana', 'Kuramuka kasanu', 'Kifula nguwo'], + 'weekdays_short' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], + 'weekdays_min' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], + 'months' => ['Mori ghwa imbiri', 'Mori ghwa kawi', 'Mori ghwa kadadu', 'Mori ghwa kana', 'Mori ghwa kasanu', 'Mori ghwa karandadu', 'Mori ghwa mfungade', 'Mori ghwa wunyanya', 'Mori ghwa ikenda', 'Mori ghwa ikumi', 'Mori ghwa ikumi na imweri', 'Mori ghwa ikumi na iwi'], + 'months_short' => ['Imb', 'Kaw', 'Kad', 'Kan', 'Kas', 'Kar', 'Mfu', 'Wun', 'Ike', 'Iku', 'Imw', 'Iwi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/de.php b/libraries/Carbon/src/Carbon/Lang/de.php new file mode 100644 index 00000000000..d3ad01e03a8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/de.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Michael Hohl + * - sheriffmarley + * - dennisoderwald + * - Timo + * - Karag2006 + * - Pete Scopes (pdscopes) + */ +return [ + 'year' => ':count Jahr|:count Jahre', + 'a_year' => 'ein Jahr|:count Jahre', + 'y' => ':count J.', + 'month' => ':count Monat|:count Monate', + 'a_month' => 'ein Monat|:count Monate', + 'm' => ':count Mon.', + 'week' => ':count Woche|:count Wochen', + 'a_week' => 'eine Woche|:count Wochen', + 'w' => ':count Wo.', + 'day' => ':count Tag|:count Tage', + 'a_day' => 'ein Tag|:count Tage', + 'd' => ':count Tg.', + 'hour' => ':count Stunde|:count Stunden', + 'a_hour' => 'eine Stunde|:count Stunden', + 'h' => ':count Std.', + 'minute' => ':count Minute|:count Minuten', + 'a_minute' => 'eine Minute|:count Minuten', + 'min' => ':count Min.', + 'second' => ':count Sekunde|:count Sekunden', + 'a_second' => 'ein paar Sekunden|:count Sekunden', + 's' => ':count Sek.', + 'millisecond' => ':count Millisekunde|:count Millisekunden', + 'a_millisecond' => 'eine Millisekunde|:count Millisekunden', + 'ms' => ':countms', + 'microsecond' => ':count Mikrosekunde|:count Mikrosekunden', + 'a_microsecond' => 'eine Mikrosekunde|:count Mikrosekunden', + 'µs' => ':countµs', + 'ago' => 'vor :time', + 'from_now' => 'in :time', + 'after' => ':time später', + 'before' => ':time zuvor', + + 'year_from_now' => ':count Jahr|:count Jahren', + 'month_from_now' => ':count Monat|:count Monaten', + 'week_from_now' => ':count Woche|:count Wochen', + 'day_from_now' => ':count Tag|:count Tagen', + 'year_ago' => ':count Jahr|:count Jahren', + 'month_ago' => ':count Monat|:count Monaten', + 'week_ago' => ':count Woche|:count Wochen', + 'day_ago' => ':count Tag|:count Tagen', + 'a_year_from_now' => 'ein Jahr|:count Jahren', + 'a_month_from_now' => 'ein Monat|:count Monaten', + 'a_week_from_now' => 'eine Woche|:count Wochen', + 'a_day_from_now' => 'ein Tag|:count Tagen', + 'a_year_ago' => 'ein Jahr|:count Jahren', + 'a_month_ago' => 'ein Monat|:count Monaten', + 'a_week_ago' => 'eine Woche|:count Wochen', + 'a_day_ago' => 'ein Tag|:count Tagen', + + 'diff_now' => 'Gerade eben', + 'diff_today' => 'heute', + 'diff_today_regexp' => 'heute(?:\\s+um)?', + 'diff_yesterday' => 'Gestern', + 'diff_yesterday_regexp' => 'gestern(?:\\s+um)?', + 'diff_tomorrow' => 'Morgen', + 'diff_tomorrow_regexp' => 'morgen(?:\\s+um)?', + 'diff_before_yesterday' => 'Vorgestern', + 'diff_after_tomorrow' => 'Übermorgen', + + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY HH:mm', + ], + + 'calendar' => [ + 'sameDay' => '[heute um] LT [Uhr]', + 'nextDay' => '[morgen um] LT [Uhr]', + 'nextWeek' => 'dddd [um] LT [Uhr]', + 'lastDay' => '[gestern um] LT [Uhr]', + 'lastWeek' => '[letzten] dddd [um] LT [Uhr]', + 'sameElse' => 'L', + ], + + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'weekdays' => ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + 'weekdays_short' => ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' und '], + 'ordinal_words' => [ + 'of' => 'im', + 'first' => 'erster', + 'second' => 'zweiter', + 'third' => 'dritter', + 'fourth' => 'vierten', + 'fifth' => 'fünfter', + 'last' => 'letzten', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/de_AT.php b/libraries/Carbon/src/Carbon/Lang/de_AT.php new file mode 100644 index 00000000000..9e5b06c442f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/de_AT.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sheriffmarley + * - Timo + * - Michael Hohl + * - Namoshek + * - Bernhard Baumrock (BernhardBaumrock) + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'months' => [ + 0 => 'Jänner', + ], + 'months_short' => [ + 0 => 'Jän', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/de_BE.php b/libraries/Carbon/src/Carbon/Lang/de_BE.php new file mode 100644 index 00000000000..d89fff17363 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/de_BE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/de_CH.php b/libraries/Carbon/src/Carbon/Lang/de_CH.php new file mode 100644 index 00000000000..2a979747338 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/de_CH.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sheriffmarley + * - Timo + * - Michael Hohl + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'weekdays_short' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/de_DE.php b/libraries/Carbon/src/Carbon/Lang/de_DE.php new file mode 100644 index 00000000000..046f924dbbf --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return require __DIR__.'/de.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/de_IT.php b/libraries/Carbon/src/Carbon/Lang/de_IT.php new file mode 100644 index 00000000000..1d2cec9cad9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/de_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Matthias Dieter Wallno:fer libc-locales@sourceware.org + */ +return require __DIR__.'/de.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/de_LI.php b/libraries/Carbon/src/Carbon/Lang/de_LI.php new file mode 100644 index 00000000000..04279d84771 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/de_LI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/de.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/de_LU.php b/libraries/Carbon/src/Carbon/Lang/de_LU.php new file mode 100644 index 00000000000..d89fff17363 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/de_LU.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/dje.php b/libraries/Carbon/src/Carbon/Lang/dje.php new file mode 100644 index 00000000000..e89fc6fe7a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dje.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Subbaahi', 'Zaarikay b'], + 'weekdays' => ['Alhadi', 'Atinni', 'Atalaata', 'Alarba', 'Alhamisi', 'Alzuma', 'Asibti'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count hari', // less reliable + 'y' => ':count hari', // less reliable + 'a_year' => ':count hari', // less reliable + + 'week' => ':count alzuma', // less reliable + 'w' => ':count alzuma', // less reliable + 'a_week' => ':count alzuma', // less reliable + + 'second' => ':count atinni', // less reliable + 's' => ':count atinni', // less reliable + 'a_second' => ':count atinni', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/doi.php b/libraries/Carbon/src/Carbon/Lang/doi.php new file mode 100644 index 00000000000..f5ee23b9c5a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/doi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/doi_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/doi_IN.php b/libraries/Carbon/src/Carbon/Lang/doi_IN.php new file mode 100644 index 00000000000..f31293c3607 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/doi_IN.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'एप्रैल', 'मेई', 'जून', 'जूलै', 'अगस्त', 'सितंबर', 'अक्तूबर', 'नवंबर', 'दिसंबर'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'एप्रैल', 'मेई', 'जून', 'जूलै', 'अगस्त', 'सितंबर', 'अक्तूबर', 'नवंबर', 'दिसंबर'], + 'weekdays' => ['ऐतबार', 'सोमबार', 'मंगलबर', 'बुधबार', 'बीरबार', 'शुक्करबार', 'श्नीचरबार'], + 'weekdays_short' => ['ऐत', 'सोम', 'मंगल', 'बुध', 'बीर', 'शुक्कर', 'श्नीचर'], + 'weekdays_min' => ['ऐत', 'सोम', 'मंगल', 'बुध', 'बीर', 'शुक्कर', 'श्नीचर'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['सञं', 'सबेर'], + + 'second' => ':count सङार', // less reliable + 's' => ':count सङार', // less reliable + 'a_second' => ':count सङार', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/dsb.php b/libraries/Carbon/src/Carbon/Lang/dsb.php new file mode 100644 index 00000000000..395a1932c0f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dsb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/dsb_DE.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/dsb_DE.php b/libraries/Carbon/src/Carbon/Lang/dsb_DE.php new file mode 100644 index 00000000000..cd057a84bc7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dsb_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from Michael Wolf bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'DD. MMMM, HH:mm [góź.]', + 'LLLL' => 'dddd, DD. MMMM YYYY, HH:mm [góź.]', + ], + 'months' => ['januara', 'februara', 'měrca', 'apryla', 'maja', 'junija', 'julija', 'awgusta', 'septembra', 'oktobra', 'nowembra', 'decembra'], + 'months_short' => ['Jan', 'Feb', 'Měr', 'Apr', 'Maj', 'Jun', 'Jul', 'Awg', 'Sep', 'Okt', 'Now', 'Dec'], + 'weekdays' => ['Njeźela', 'Pónjeźele', 'Wałtora', 'Srjoda', 'Stwórtk', 'Pětk', 'Sobota'], + 'weekdays_short' => ['Nj', 'Pó', 'Wa', 'Sr', 'St', 'Pě', 'So'], + 'weekdays_min' => ['Nj', 'Pó', 'Wa', 'Sr', 'St', 'Pě', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count lěto', + 'y' => ':count lěto', + 'a_year' => ':count lěto', + + 'month' => ':count mjasec', + 'm' => ':count mjasec', + 'a_month' => ':count mjasec', + + 'week' => ':count tyźeń', + 'w' => ':count tyźeń', + 'a_week' => ':count tyźeń', + + 'day' => ':count źeń', + 'd' => ':count źeń', + 'a_day' => ':count źeń', + + 'hour' => ':count góźina', + 'h' => ':count góźina', + 'a_hour' => ':count góźina', + + 'minute' => ':count minuta', + 'min' => ':count minuta', + 'a_minute' => ':count minuta', + + 'second' => ':count drugi', + 's' => ':count drugi', + 'a_second' => ':count drugi', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/dua.php b/libraries/Carbon/src/Carbon/Lang/dua.php new file mode 100644 index 00000000000..1dca0d4a4c0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dua.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['idiɓa', 'ebyámu'], + 'weekdays' => ['éti', 'mɔ́sú', 'kwasú', 'mukɔ́sú', 'ŋgisú', 'ɗónɛsú', 'esaɓasú'], + 'weekdays_short' => ['ét', 'mɔ́s', 'kwa', 'muk', 'ŋgi', 'ɗón', 'esa'], + 'weekdays_min' => ['ét', 'mɔ́s', 'kwa', 'muk', 'ŋgi', 'ɗón', 'esa'], + 'months' => ['dimɔ́di', 'ŋgɔndɛ', 'sɔŋɛ', 'diɓáɓá', 'emiasele', 'esɔpɛsɔpɛ', 'madiɓɛ́díɓɛ́', 'diŋgindi', 'nyɛtɛki', 'mayésɛ́', 'tiníní', 'eláŋgɛ́'], + 'months_short' => ['di', 'ŋgɔn', 'sɔŋ', 'diɓ', 'emi', 'esɔ', 'mad', 'diŋ', 'nyɛt', 'may', 'tin', 'elá'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count ma mbu', // less reliable + 'y' => ':count ma mbu', // less reliable + 'a_year' => ':count ma mbu', // less reliable + + 'month' => ':count myo̱di', // less reliable + 'm' => ':count myo̱di', // less reliable + 'a_month' => ':count myo̱di', // less reliable + + 'week' => ':count woki', // less reliable + 'w' => ':count woki', // less reliable + 'a_week' => ':count woki', // less reliable + + 'day' => ':count buńa', // less reliable + 'd' => ':count buńa', // less reliable + 'a_day' => ':count buńa', // less reliable + + 'hour' => ':count ma awa', // less reliable + 'h' => ':count ma awa', // less reliable + 'a_hour' => ':count ma awa', // less reliable + + 'minute' => ':count minuti', // less reliable + 'min' => ':count minuti', // less reliable + 'a_minute' => ':count minuti', // less reliable + + 'second' => ':count maba', // less reliable + 's' => ':count maba', // less reliable + 'a_second' => ':count maba', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/dv.php b/libraries/Carbon/src/Carbon/Lang/dv.php new file mode 100644 index 00000000000..291b92f2ab8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dv.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'ޖެނުއަރީ', + 'ފެބްރުއަރީ', + 'މާރިޗު', + 'އޭޕްރީލު', + 'މޭ', + 'ޖޫން', + 'ޖުލައި', + 'އޯގަސްޓު', + 'ސެޕްޓެމްބަރު', + 'އޮކްޓޯބަރު', + 'ނޮވެމްބަރު', + 'ޑިސެމްބަރު', +]; + +$weekdays = [ + 'އާދިއްތަ', + 'ހޯމަ', + 'އަންގާރަ', + 'ބުދަ', + 'ބުރާސްފަތި', + 'ހުކުރު', + 'ހޮނިހިރު', +]; + +/* + * Authors: + * - Josh Soref + * - Jawish Hameed + */ +return [ + 'year' => ':count '.'އަހަރު', + 'a_year' => '{1}'.'އަހަރެއް'.'|:count '.'އަހަރު', + 'month' => ':count '.'މަސް', + 'a_month' => '{1}'.'މަހެއް'.'|:count '.'މަސް', + 'week' => ':count '.'ހަފްތާ', + 'a_week' => '{1}'.'ސިކުންތުކޮޅެއް'.'|:count '.'ހަފްތާ', + 'day' => ':count '.'ދުވަސް', + 'a_day' => '{1}'.'ދުވަހެއް'.'|:count '.'ދުވަސް', + 'hour' => ':count '.'ގަޑިއިރު', + 'a_hour' => '{1}'.'ގަޑިއިރެއް'.'|:count '.'ގަޑިއިރު', + 'minute' => ':count '.'މިނިޓު', + 'a_minute' => '{1}'.'މިނިޓެއް'.'|:count '.'މިނިޓު', + 'second' => ':count '.'ސިކުންތު', + 'a_second' => '{1}'.'ސިކުންތުކޮޅެއް'.'|:count '.'ސިކުންތު', + 'ago' => 'ކުރިން :time', + 'from_now' => 'ތެރޭގައި :time', + 'after' => ':time ފަހުން', + 'before' => ':time ކުރި', + 'diff_yesterday' => 'އިއްޔެ', + 'diff_today' => 'މިއަދު', + 'diff_tomorrow' => 'މާދަމާ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[މިއަދު] LT', + 'nextDay' => '[މާދަމާ] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[އިއްޔެ] LT', + 'lastWeek' => '[ފާއިތުވި] dddd LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['މކ', 'މފ'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => ['އާދި', 'ހޯމަ', 'އަން', 'ބުދަ', 'ބުރާ', 'ހުކު', 'ހޮނި'], + 'list' => [', ', ' އަދި '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/dv_MV.php b/libraries/Carbon/src/Carbon/Lang/dv_MV.php new file mode 100644 index 00000000000..835e807c7ec --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dv_MV.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ahmed Ali + */ + +$months = [ + 'ޖެނުއަރީ', + 'ފެބްރުއަރީ', + 'މާރިޗު', + 'އޭޕްރީލު', + 'މޭ', + 'ޖޫން', + 'ޖުލައި', + 'އޯގަސްޓު', + 'ސެޕްޓެމްބަރު', + 'އޮކްޓޯބަރު', + 'ނޮވެމްބަރު', + 'ޑިސެމްބަރު', +]; + +$weekdays = [ + 'އާދިއްތަ', + 'ހޯމަ', + 'އަންގާރަ', + 'ބުދަ', + 'ބުރާސްފަތި', + 'ހުކުރު', + 'ހޮނިހިރު', +]; + +return [ + 'year' => '{0}އަހަރެއް|[1,Inf]:count އަހަރު', + 'y' => '{0}އަހަރެއް|[1,Inf]:count އަހަރު', + 'month' => '{0}މައްސަރެއް|[1,Inf]:count މަސް', + 'm' => '{0}މައްސަރެއް|[1,Inf]:count މަސް', + 'week' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ', + 'w' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ', + 'day' => '{0}ދުވަސް|[1,Inf]:count ދުވަސް', + 'd' => '{0}ދުވަސް|[1,Inf]:count ދުވަސް', + 'hour' => '{0}ގަޑިއިރެއް|[1,Inf]:count ގަޑި', + 'h' => '{0}ގަޑިއިރެއް|[1,Inf]:count ގަޑި', + 'minute' => '{0}މިނެޓެއް|[1,Inf]:count މިނެޓް', + 'min' => '{0}މިނެޓެއް|[1,Inf]:count މިނެޓް', + 'second' => '{0}ސިކުންތެއް|[1,Inf]:count ސިކުންތު', + 's' => '{0}ސިކުންތެއް|[1,Inf]:count ސިކުންތު', + 'ago' => ':time ކުރިން', + 'from_now' => ':time ފަހުން', + 'after' => ':time ފަހުން', + 'before' => ':time ކުރި', + 'diff_yesterday' => 'އިއްޔެ', + 'diff_today' => 'މިއަދު', + 'diff_tomorrow' => 'މާދަމާ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[މިއަދު] LT', + 'nextDay' => '[މާދަމާ] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[އިއްޔެ] LT', + 'lastWeek' => '[ފާއިތުވި] dddd LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['މކ', 'މފ'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => ['އާދި', 'ހޯމަ', 'އަން', 'ބުދަ', 'ބުރާ', 'ހުކު', 'ހޮނި'], + 'list' => [', ', ' އަދި '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/dyo.php b/libraries/Carbon/src/Carbon/Lang/dyo.php new file mode 100644 index 00000000000..bd0cdf80c29 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dyo.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Dimas', 'Teneŋ', 'Talata', 'Alarbay', 'Aramisay', 'Arjuma', 'Sibiti'], + 'weekdays_short' => ['Dim', 'Ten', 'Tal', 'Ala', 'Ara', 'Arj', 'Sib'], + 'weekdays_min' => ['Dim', 'Ten', 'Tal', 'Ala', 'Ara', 'Arj', 'Sib'], + 'months' => ['Sanvie', 'Fébirie', 'Mars', 'Aburil', 'Mee', 'Sueŋ', 'Súuyee', 'Ut', 'Settembar', 'Oktobar', 'Novembar', 'Disambar'], + 'months_short' => ['Sa', 'Fe', 'Ma', 'Ab', 'Me', 'Su', 'Sú', 'Ut', 'Se', 'Ok', 'No', 'De'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/dz.php b/libraries/Carbon/src/Carbon/Lang/dz.php new file mode 100644 index 00000000000..a68dc5fc73e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dz.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/dz_BT.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/dz_BT.php b/libraries/Carbon/src/Carbon/Lang/dz_BT.php new file mode 100644 index 00000000000..e5868d6eb7d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/dz_BT.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sherubtse College bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'པསྱི་ལོYYཟལMMཚེསDD', + ], + 'months' => ['ཟླ་བ་དང་པ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་ཕ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུནཔ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], + 'months_short' => ['ཟླ་༡', 'ཟླ་༢', 'ཟླ་༣', 'ཟླ་༤', 'ཟླ་༥', 'ཟླ་༦', 'ཟླ་༧', 'ཟླ་༨', 'ཟླ་༩', 'ཟླ་༡༠', 'ཟླ་༡༡', 'ཟླ་༡༢'], + 'weekdays' => ['གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་ཕ་', 'གཟའ་པུར་བུ་', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་ཕ་', 'གཟའ་ཉི་མ་'], + 'weekdays_short' => ['ཟླ་', 'མིར་', 'ལྷག་', 'པུར་', 'སངས་', 'སྤེན་', 'ཉི་'], + 'weekdays_min' => ['ཟླ་', 'མིར་', 'ལྷག་', 'པུར་', 'སངས་', 'སྤེན་', 'ཉི་'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ངས་ཆ', 'ཕྱི་ཆ'], + + 'year' => ':count ཆརཔ', // less reliable + 'y' => ':count ཆརཔ', // less reliable + 'a_year' => ':count ཆརཔ', // less reliable + + 'month' => ':count ཟླ་བ', // less reliable + 'm' => ':count ཟླ་བ', // less reliable + 'a_month' => ':count ཟླ་བ', // less reliable + + 'day' => ':count ཉི', // less reliable + 'd' => ':count ཉི', // less reliable + 'a_day' => ':count ཉི', // less reliable + + 'second' => ':count ཆ', // less reliable + 's' => ':count ཆ', // less reliable + 'a_second' => ':count ཆ', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ebu.php b/libraries/Carbon/src/Carbon/Lang/ebu.php new file mode 100644 index 00000000000..6c59f4b6265 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ebu.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['KI', 'UT'], + 'weekdays' => ['Kiumia', 'Njumatatu', 'Njumaine', 'Njumatano', 'Aramithi', 'Njumaa', 'NJumamothii'], + 'weekdays_short' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], + 'weekdays_min' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], + 'months' => ['Mweri wa mbere', 'Mweri wa kaĩri', 'Mweri wa kathatũ', 'Mweri wa kana', 'Mweri wa gatano', 'Mweri wa gatantatũ', 'Mweri wa mũgwanja', 'Mweri wa kanana', 'Mweri wa kenda', 'Mweri wa ikũmi', 'Mweri wa ikũmi na ũmwe', 'Mweri wa ikũmi na Kaĩrĩ'], + 'months_short' => ['Mbe', 'Kai', 'Kat', 'Kan', 'Gat', 'Gan', 'Mug', 'Knn', 'Ken', 'Iku', 'Imw', 'Igi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ee.php b/libraries/Carbon/src/Carbon/Lang/ee.php new file mode 100644 index 00000000000..3a64e8f939e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ee.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ŋ', 'ɣ'], + 'weekdays' => ['kɔsiɖa', 'dzoɖa', 'blaɖa', 'kuɖa', 'yawoɖa', 'fiɖa', 'memleɖa'], + 'weekdays_short' => ['kɔs', 'dzo', 'bla', 'kuɖ', 'yaw', 'fiɖ', 'mem'], + 'weekdays_min' => ['kɔs', 'dzo', 'bla', 'kuɖ', 'yaw', 'fiɖ', 'mem'], + 'months' => ['dzove', 'dzodze', 'tedoxe', 'afɔfĩe', 'dama', 'masa', 'siamlɔm', 'deasiamime', 'anyɔnyɔ', 'kele', 'adeɛmekpɔxe', 'dzome'], + 'months_short' => ['dzv', 'dzd', 'ted', 'afɔ', 'dam', 'mas', 'sia', 'dea', 'any', 'kel', 'ade', 'dzm'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'a [ga] h:mm', + 'LTS' => 'a [ga] h:mm:ss', + 'L' => 'M/D/YYYY', + 'LL' => 'MMM D [lia], YYYY', + 'LLL' => 'a [ga] h:mm MMMM D [lia] YYYY', + 'LLLL' => 'a [ga] h:mm dddd, MMMM D [lia] YYYY', + ], + + 'year' => 'ƒe :count', + 'y' => 'ƒe :count', + 'a_year' => 'ƒe :count', + + 'month' => 'ɣleti :count', + 'm' => 'ɣleti :count', + 'a_month' => 'ɣleti :count', + + 'week' => 'kwasiɖa :count', + 'w' => 'kwasiɖa :count', + 'a_week' => 'kwasiɖa :count', + + 'day' => 'ŋkeke :count', + 'd' => 'ŋkeke :count', + 'a_day' => 'ŋkeke :count', + + 'hour' => 'gaƒoƒo :count', + 'h' => 'gaƒoƒo :count', + 'a_hour' => 'gaƒoƒo :count', + + 'minute' => 'miniti :count', // less reliable + 'min' => 'miniti :count', // less reliable + 'a_minute' => 'miniti :count', // less reliable + + 'second' => 'sɛkɛnd :count', // less reliable + 's' => 'sɛkɛnd :count', // less reliable + 'a_second' => 'sɛkɛnd :count', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ee_TG.php b/libraries/Carbon/src/Carbon/Lang/ee_TG.php new file mode 100644 index 00000000000..6cf30765c88 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ee_TG.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ee.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'LLL' => 'HH:mm MMMM D [lia] YYYY', + 'LLLL' => 'HH:mm dddd, MMMM D [lia] YYYY', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/el.php b/libraries/Carbon/src/Carbon/Lang/el.php new file mode 100644 index 00000000000..afb926a9e79 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/el.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alessandro Di Felice + * - François B + * - Tim Fish + * - Gabriel Monteagudo + * - JD Isaacks + * - yiannisdesp + * - Ilias Kasmeridis (iliaskasm) + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count χρόνος|:count χρόνια', + 'a_year' => 'ένας χρόνος|:count χρόνια', + 'y' => ':count χρ.', + 'month' => ':count μήνας|:count μήνες', + 'a_month' => 'ένας μήνας|:count μήνες', + 'm' => ':count μήν.', + 'week' => ':count εβδομάδα|:count εβδομάδες', + 'a_week' => 'μια εβδομάδα|:count εβδομάδες', + 'w' => ':count εβδ.', + 'day' => ':count μέρα|:count μέρες', + 'a_day' => 'μία μέρα|:count μέρες', + 'd' => ':count μέρ.', + 'hour' => ':count ώρα|:count ώρες', + 'a_hour' => 'μία ώρα|:count ώρες', + 'h' => ':count ώρα|:count ώρες', + 'minute' => ':count λεπτό|:count λεπτά', + 'a_minute' => 'ένα λεπτό|:count λεπτά', + 'min' => ':count λεπ.', + 'second' => ':count δευτερόλεπτο|:count δευτερόλεπτα', + 'a_second' => 'λίγα δευτερόλεπτα|:count δευτερόλεπτα', + 's' => ':count δευ.', + 'ago' => 'πριν :time', + 'from_now' => 'σε :time', + 'after' => ':time μετά', + 'before' => ':time πριν', + 'diff_now' => 'τώρα', + 'diff_today' => 'Σήμερα', + 'diff_today_regexp' => 'Σήμερα(?:\\s+{})?', + 'diff_yesterday' => 'χθες', + 'diff_yesterday_regexp' => 'Χθες(?:\\s+{})?', + 'diff_tomorrow' => 'αύριο', + 'diff_tomorrow_regexp' => 'Αύριο(?:\\s+{})?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Σήμερα {}] LT', + 'nextDay' => '[Αύριο {}] LT', + 'nextWeek' => 'dddd [{}] LT', + 'lastDay' => '[Χθες {}] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 6: + return '[το προηγούμενο] dddd [{}] LT'; + default: + return '[την προηγούμενη] dddd [{}] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberη', + 'meridiem' => ['ΠΜ', 'ΜΜ', 'πμ', 'μμ'], + 'months' => ['Ιανουαρίου', 'Φεβρουαρίου', 'Μαρτίου', 'Απριλίου', 'Μαΐου', 'Ιουνίου', 'Ιουλίου', 'Αυγούστου', 'Σεπτεμβρίου', 'Οκτωβρίου', 'Νοεμβρίου', 'Δεκεμβρίου'], + 'months_standalone' => ['Ιανουάριος', 'Φεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'Ιούνιος', 'Ιούλιος', 'Αύγουστος', 'Σεπτέμβριος', 'Οκτώβριος', 'Νοέμβριος', 'Δεκέμβριος'], + 'months_regexp' => '/(D[oD]?[\s,]+MMMM|L{2,4}|l{2,4})/', + 'months_short' => ['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μαϊ', 'Ιουν', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'], + 'weekdays' => ['Κυριακή', 'Δευτέρα', 'Τρίτη', 'Τετάρτη', 'Πέμπτη', 'Παρασκευή', 'Σάββατο'], + 'weekdays_short' => ['Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ'], + 'weekdays_min' => ['Κυ', 'Δε', 'Τρ', 'Τε', 'Πε', 'Πα', 'Σα'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' και '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/el_CY.php b/libraries/Carbon/src/Carbon/Lang/el_CY.php new file mode 100644 index 00000000000..7988c400ed0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/el_CY.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Greek Debian Translation Team bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/el.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/el_GR.php b/libraries/Carbon/src/Carbon/Lang/el_GR.php new file mode 100644 index 00000000000..57e6ec5afd9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/el_GR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/el.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en.php b/libraries/Carbon/src/Carbon/Lang/en.php new file mode 100644 index 00000000000..b4691411737 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Milos Sakovic + * - Paul + * - Pete Scopes (pdscopes) + */ +return [ + /* + * {1}, {0} and ]1,Inf[ are not needed as it's the default for English pluralization. + * But as some languages are using en.php as a fallback, it's better to specify it + * explicitly so those languages also fallback to English pluralization when a unit + * is missing. + */ + 'year' => '{1}:count year|{0}:count years|]1,Inf[:count years', + 'a_year' => '{1}a year|{0}:count years|]1,Inf[:count years', + 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + 'month' => '{1}:count month|{0}:count months|]1,Inf[:count months', + 'a_month' => '{1}a month|{0}:count months|]1,Inf[:count months', + 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + 'week' => '{1}:count week|{0}:count weeks|]1,Inf[:count weeks', + 'a_week' => '{1}a week|{0}:count weeks|]1,Inf[:count weeks', + 'w' => ':countw', + 'day' => '{1}:count day|{0}:count days|]1,Inf[:count days', + 'a_day' => '{1}a day|{0}:count days|]1,Inf[:count days', + 'd' => ':countd', + 'hour' => '{1}:count hour|{0}:count hours|]1,Inf[:count hours', + 'a_hour' => '{1}an hour|{0}:count hours|]1,Inf[:count hours', + 'h' => ':counth', + 'minute' => '{1}:count minute|{0}:count minutes|]1,Inf[:count minutes', + 'a_minute' => '{1}a minute|{0}:count minutes|]1,Inf[:count minutes', + 'min' => ':countm', + 'second' => '{1}:count second|{0}:count seconds|]1,Inf[:count seconds', + 'a_second' => '{1}a few seconds|{0}:count seconds|]1,Inf[:count seconds', + 's' => ':counts', + 'millisecond' => '{1}:count millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'ms' => ':countms', + 'microsecond' => '{1}:count microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'a_microsecond' => '{1}a microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'µs' => ':countµs', + 'ago' => ':time ago', + 'from_now' => ':time from now', + 'after' => ':time after', + 'before' => ':time before', + 'diff_now' => 'just now', + 'diff_today' => 'today', + 'diff_yesterday' => 'yesterday', + 'diff_tomorrow' => 'tomorrow', + 'diff_before_yesterday' => 'before yesterday', + 'diff_after_tomorrow' => 'after tomorrow', + 'period_recurrences' => '{1}once|{0}:count times|]1,Inf[:count times', + 'period_interval' => 'every :interval', + 'period_start_date' => 'from :date', + 'period_end_date' => 'to :date', + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'th' : ( + ($lastDigit === 1) ? 'st' : ( + ($lastDigit === 2) ? 'nd' : ( + ($lastDigit === 3) ? 'rd' : 'th' + ) + ) + ) + ); + }, + 'list' => [', ', ' and '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/en_001.php b/libraries/Carbon/src/Carbon/Lang/en_001.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_001.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_150.php b/libraries/Carbon/src/Carbon/Lang/en_150.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_150.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_AG.php b/libraries/Carbon/src/Carbon/Lang/en_AG.php new file mode 100644 index 00000000000..166b17b1a4f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_AG.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_AI.php b/libraries/Carbon/src/Carbon/Lang/en_AI.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_AI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_AS.php b/libraries/Carbon/src/Carbon/Lang/en_AS.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_AS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_AT.php b/libraries/Carbon/src/Carbon/Lang/en_AT.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_AT.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_AU.php b/libraries/Carbon/src/Carbon/Lang/en_AU.php new file mode 100644 index 00000000000..bb69c4f001d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_AU.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_BB.php b/libraries/Carbon/src/Carbon/Lang/en_BB.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_BB.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_BE.php b/libraries/Carbon/src/Carbon/Lang/en_BE.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_BE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_BI.php b/libraries/Carbon/src/Carbon/Lang/en_BI.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_BI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_BM.php b/libraries/Carbon/src/Carbon/Lang/en_BM.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_BM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_BS.php b/libraries/Carbon/src/Carbon/Lang/en_BS.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_BS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_BW.php b/libraries/Carbon/src/Carbon/Lang/en_BW.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_BW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_BZ.php b/libraries/Carbon/src/Carbon/Lang/en_BZ.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_BZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_CA.php b/libraries/Carbon/src/Carbon/Lang/en_CA.php new file mode 100644 index 00000000000..b8a7cb1a546 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_CA.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Zhan Tong Zhang + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY h:mm A', + 'LLLL' => 'dddd, MMMM D, YYYY h:mm A', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_CC.php b/libraries/Carbon/src/Carbon/Lang/en_CC.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_CC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_CH.php b/libraries/Carbon/src/Carbon/Lang/en_CH.php new file mode 100644 index 00000000000..671f522d623 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_CH.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_CK.php b/libraries/Carbon/src/Carbon/Lang/en_CK.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_CK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_CM.php b/libraries/Carbon/src/Carbon/Lang/en_CM.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_CM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_CX.php b/libraries/Carbon/src/Carbon/Lang/en_CX.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_CX.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_CY.php b/libraries/Carbon/src/Carbon/Lang/en_CY.php new file mode 100644 index 00000000000..ed9b1f895d9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_CY.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - NehaGautam + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_DE.php b/libraries/Carbon/src/Carbon/Lang/en_DE.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_DE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_DG.php b/libraries/Carbon/src/Carbon/Lang/en_DG.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_DG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_DK.php b/libraries/Carbon/src/Carbon/Lang/en_DK.php new file mode 100644 index 00000000000..ddb7a9d7362 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_DK.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Danish Standards Association bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_DM.php b/libraries/Carbon/src/Carbon/Lang/en_DM.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_DM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_ER.php b/libraries/Carbon/src/Carbon/Lang/en_ER.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_ER.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_FI.php b/libraries/Carbon/src/Carbon/Lang/en_FI.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_FI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_FJ.php b/libraries/Carbon/src/Carbon/Lang/en_FJ.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_FJ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_FK.php b/libraries/Carbon/src/Carbon/Lang/en_FK.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_FK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_FM.php b/libraries/Carbon/src/Carbon/Lang/en_FM.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_FM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_GB.php b/libraries/Carbon/src/Carbon/Lang/en_GB.php new file mode 100644 index 00000000000..2273bdcc284 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_GB.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_GD.php b/libraries/Carbon/src/Carbon/Lang/en_GD.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_GD.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_GG.php b/libraries/Carbon/src/Carbon/Lang/en_GG.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_GG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_GH.php b/libraries/Carbon/src/Carbon/Lang/en_GH.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_GH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_GI.php b/libraries/Carbon/src/Carbon/Lang/en_GI.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_GI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_GM.php b/libraries/Carbon/src/Carbon/Lang/en_GM.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_GM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_GU.php b/libraries/Carbon/src/Carbon/Lang/en_GU.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_GU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_GY.php b/libraries/Carbon/src/Carbon/Lang/en_GY.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_GY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_HK.php b/libraries/Carbon/src/Carbon/Lang/en_HK.php new file mode 100644 index 00000000000..6aba4ba83ce --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_HK.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_IE.php b/libraries/Carbon/src/Carbon/Lang/en_IE.php new file mode 100644 index 00000000000..6900750a53d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_IE.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Martin McWhorter + * - François B + * - Chris Cartlidge + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_IL.php b/libraries/Carbon/src/Carbon/Lang/en_IL.php new file mode 100644 index 00000000000..c2466aa9932 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_IL.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Yoav Amit + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_IM.php b/libraries/Carbon/src/Carbon/Lang/en_IM.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_IM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_IN.php b/libraries/Carbon/src/Carbon/Lang/en_IN.php new file mode 100644 index 00000000000..a9f7f7c1052 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_IO.php b/libraries/Carbon/src/Carbon/Lang/en_IO.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_IO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_ISO.php b/libraries/Carbon/src/Carbon/Lang/en_ISO.php new file mode 100644 index 00000000000..0b0b9d7fba1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_ISO.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_JE.php b/libraries/Carbon/src/Carbon/Lang/en_JE.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_JE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_JM.php b/libraries/Carbon/src/Carbon/Lang/en_JM.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_JM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_KE.php b/libraries/Carbon/src/Carbon/Lang/en_KE.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_KE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_KI.php b/libraries/Carbon/src/Carbon/Lang/en_KI.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_KI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_KN.php b/libraries/Carbon/src/Carbon/Lang/en_KN.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_KN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_KY.php b/libraries/Carbon/src/Carbon/Lang/en_KY.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_KY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_LC.php b/libraries/Carbon/src/Carbon/Lang/en_LC.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_LC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_LR.php b/libraries/Carbon/src/Carbon/Lang/en_LR.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_LR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_LS.php b/libraries/Carbon/src/Carbon/Lang/en_LS.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_LS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_MG.php b/libraries/Carbon/src/Carbon/Lang/en_MG.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_MH.php b/libraries/Carbon/src/Carbon/Lang/en_MH.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_MO.php b/libraries/Carbon/src/Carbon/Lang/en_MO.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_MP.php b/libraries/Carbon/src/Carbon/Lang/en_MP.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MP.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_MS.php b/libraries/Carbon/src/Carbon/Lang/en_MS.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_MT.php b/libraries/Carbon/src/Carbon/Lang/en_MT.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_MU.php b/libraries/Carbon/src/Carbon/Lang/en_MU.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_MW.php b/libraries/Carbon/src/Carbon/Lang/en_MW.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_MY.php b/libraries/Carbon/src/Carbon/Lang/en_MY.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_MY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_NA.php b/libraries/Carbon/src/Carbon/Lang/en_NA.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_NA.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_NF.php b/libraries/Carbon/src/Carbon/Lang/en_NF.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_NF.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_NG.php b/libraries/Carbon/src/Carbon/Lang/en_NG.php new file mode 100644 index 00000000000..9c4ddf6acab --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_NG.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_NL.php b/libraries/Carbon/src/Carbon/Lang/en_NL.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_NL.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_NR.php b/libraries/Carbon/src/Carbon/Lang/en_NR.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_NR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_NU.php b/libraries/Carbon/src/Carbon/Lang/en_NU.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_NU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_NZ.php b/libraries/Carbon/src/Carbon/Lang/en_NZ.php new file mode 100644 index 00000000000..53e321e6b95 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_NZ.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Mayank Badola + * - Luke McGregor + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_PG.php b/libraries/Carbon/src/Carbon/Lang/en_PG.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_PG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_PH.php b/libraries/Carbon/src/Carbon/Lang/en_PH.php new file mode 100644 index 00000000000..6aba4ba83ce --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_PH.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_PK.php b/libraries/Carbon/src/Carbon/Lang/en_PK.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_PK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_PN.php b/libraries/Carbon/src/Carbon/Lang/en_PN.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_PN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_PR.php b/libraries/Carbon/src/Carbon/Lang/en_PR.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_PR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_PW.php b/libraries/Carbon/src/Carbon/Lang/en_PW.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_PW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_RW.php b/libraries/Carbon/src/Carbon/Lang/en_RW.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_RW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SB.php b/libraries/Carbon/src/Carbon/Lang/en_SB.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SB.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SC.php b/libraries/Carbon/src/Carbon/Lang/en_SC.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SD.php b/libraries/Carbon/src/Carbon/Lang/en_SD.php new file mode 100644 index 00000000000..4316e3c2da1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SD.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SE.php b/libraries/Carbon/src/Carbon/Lang/en_SE.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SG.php b/libraries/Carbon/src/Carbon/Lang/en_SG.php new file mode 100644 index 00000000000..0c54c0172f5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SG.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SH.php b/libraries/Carbon/src/Carbon/Lang/en_SH.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SI.php b/libraries/Carbon/src/Carbon/Lang/en_SI.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SL.php b/libraries/Carbon/src/Carbon/Lang/en_SL.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SL.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SS.php b/libraries/Carbon/src/Carbon/Lang/en_SS.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SX.php b/libraries/Carbon/src/Carbon/Lang/en_SX.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SX.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_SZ.php b/libraries/Carbon/src/Carbon/Lang/en_SZ.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_SZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_TC.php b/libraries/Carbon/src/Carbon/Lang/en_TC.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_TC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_TK.php b/libraries/Carbon/src/Carbon/Lang/en_TK.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_TK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_TO.php b/libraries/Carbon/src/Carbon/Lang/en_TO.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_TO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_TT.php b/libraries/Carbon/src/Carbon/Lang/en_TT.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_TT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_TV.php b/libraries/Carbon/src/Carbon/Lang/en_TV.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_TV.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_TZ.php b/libraries/Carbon/src/Carbon/Lang/en_TZ.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_TZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_UG.php b/libraries/Carbon/src/Carbon/Lang/en_UG.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_UG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_UM.php b/libraries/Carbon/src/Carbon/Lang/en_UM.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_UM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_US.php b/libraries/Carbon/src/Carbon/Lang/en_US.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_US.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_US_Posix.php b/libraries/Carbon/src/Carbon/Lang/en_US_Posix.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_US_Posix.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_VC.php b/libraries/Carbon/src/Carbon/Lang/en_VC.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_VC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_VG.php b/libraries/Carbon/src/Carbon/Lang/en_VG.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_VG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_VI.php b/libraries/Carbon/src/Carbon/Lang/en_VI.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_VI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_VU.php b/libraries/Carbon/src/Carbon/Lang/en_VU.php new file mode 100644 index 00000000000..46563ce5529 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_VU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_WS.php b/libraries/Carbon/src/Carbon/Lang/en_WS.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_WS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/en_ZA.php b/libraries/Carbon/src/Carbon/Lang/en_ZA.php new file mode 100644 index 00000000000..67e8e647c66 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_ZA.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_ZM.php b/libraries/Carbon/src/Carbon/Lang/en_ZM.php new file mode 100644 index 00000000000..c00566288d1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_ZM.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANLoc Martin Benjamin locales@africanlocalization.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/en_ZW.php b/libraries/Carbon/src/Carbon/Lang/en_ZW.php new file mode 100644 index 00000000000..6ea7739fc72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/en_ZW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/eo.php b/libraries/Carbon/src/Carbon/Lang/eo.php new file mode 100644 index 00000000000..628dc6613b8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/eo.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Mia Nordentoft + * - JD Isaacks + */ +return [ + 'year' => ':count jaro|:count jaroj', + 'a_year' => 'jaro|:count jaroj', + 'y' => ':count j.', + 'month' => ':count monato|:count monatoj', + 'a_month' => 'monato|:count monatoj', + 'm' => ':count mo.', + 'week' => ':count semajno|:count semajnoj', + 'a_week' => 'semajno|:count semajnoj', + 'w' => ':count sem.', + 'day' => ':count tago|:count tagoj', + 'a_day' => 'tago|:count tagoj', + 'd' => ':count t.', + 'hour' => ':count horo|:count horoj', + 'a_hour' => 'horo|:count horoj', + 'h' => ':count h.', + 'minute' => ':count minuto|:count minutoj', + 'a_minute' => 'minuto|:count minutoj', + 'min' => ':count min.', + 'second' => ':count sekundo|:count sekundoj', + 'a_second' => 'sekundoj|:count sekundoj', + 's' => ':count sek.', + 'ago' => 'antaŭ :time', + 'from_now' => 'post :time', + 'after' => ':time poste', + 'before' => ':time antaŭe', + 'diff_yesterday' => 'Hieraŭ', + 'diff_yesterday_regexp' => 'Hieraŭ(?:\\s+je)?', + 'diff_today' => 'Hodiaŭ', + 'diff_today_regexp' => 'Hodiaŭ(?:\\s+je)?', + 'diff_tomorrow' => 'Morgaŭ', + 'diff_tomorrow_regexp' => 'Morgaŭ(?:\\s+je)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'D[-a de] MMMM, YYYY', + 'LLL' => 'D[-a de] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, [la] D[-a de] MMMM, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hodiaŭ je] LT', + 'nextDay' => '[Morgaŭ je] LT', + 'nextWeek' => 'dddd [je] LT', + 'lastDay' => '[Hieraŭ je] LT', + 'lastWeek' => '[pasinta] dddd [je] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numbera', + 'meridiem' => ['a.t.m.', 'p.t.m.'], + 'months' => ['januaro', 'februaro', 'marto', 'aprilo', 'majo', 'junio', 'julio', 'aŭgusto', 'septembro', 'oktobro', 'novembro', 'decembro'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aŭg', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['dimanĉo', 'lundo', 'mardo', 'merkredo', 'ĵaŭdo', 'vendredo', 'sabato'], + 'weekdays_short' => ['dim', 'lun', 'mard', 'merk', 'ĵaŭ', 'ven', 'sab'], + 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'ĵa', 've', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' kaj '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/es.php b/libraries/Carbon/src/Carbon/Lang/es.php new file mode 100644 index 00000000000..35e8c7f3cb3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - kostas + * - François B + * - Tim Fish + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + * - Jorge Y. Castillo + * - Víctor Díaz + * - Diego + * - Sebastian Thierer + * - quinterocesar + * - Daniel Commesse Liévanos (danielcommesse) + * - Pete Scopes (pdscopes) + * - gam04 + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count año|:count años', + 'a_year' => 'un año|:count años', + 'y' => ':count año|:count años', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count semana|:count semanas', + 'a_week' => 'una semana|:count semanas', + 'w' => ':countsem', + 'day' => ':count día|:count días', + 'a_day' => 'un día|:count días', + 'd' => ':countd', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'una hora|:count horas', + 'h' => ':counth', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'un minuto|:count minutos', + 'min' => ':countm', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'unos segundos|:count segundos', + 's' => ':counts', + 'millisecond' => ':count milisegundo|:count milisegundos', + 'a_millisecond' => 'un milisegundo|:count milisegundos', + 'ms' => ':countms', + 'microsecond' => ':count microsegundo|:count microsegundos', + 'a_microsecond' => 'un microsegundo|:count microsegundos', + 'µs' => ':countµs', + 'ago' => 'hace :time', + 'from_now' => 'en :time', + 'after' => ':time después', + 'before' => ':time antes', + 'diff_now' => 'ahora mismo', + 'diff_today' => 'hoy', + 'diff_today_regexp' => 'hoy(?:\\s+a)?(?:\\s+las)?', + 'diff_yesterday' => 'ayer', + 'diff_yesterday_regexp' => 'ayer(?:\\s+a)?(?:\\s+las)?', + 'diff_tomorrow' => 'mañana', + 'diff_tomorrow_regexp' => 'mañana(?:\\s+a)?(?:\\s+las)?', + 'diff_before_yesterday' => 'anteayer', + 'diff_after_tomorrow' => 'pasado mañana', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY H:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[hoy a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[mañana a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd [a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[ayer a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[el] dddd [pasado a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'sameElse' => 'L', + ], + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'mmm_suffix' => '.', + 'ordinal' => ':numberº', + 'weekdays' => ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'], + 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'mié.', 'jue.', 'vie.', 'sáb.'], + 'weekdays_min' => ['do', 'lu', 'ma', 'mi', 'ju', 'vi', 'sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' y '], + 'meridiem' => ['a. m.', 'p. m.'], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'primer', + 'second' => 'segundo', + 'third' => 'tercer', + 'fourth' => 'cuarto', + 'fifth' => 'quinto', + 'last' => 'último', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/es_419.php b/libraries/Carbon/src/Carbon/Lang/es_419.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_419.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_AR.php b/libraries/Carbon/src/Carbon/Lang/es_AR.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_AR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_BO.php b/libraries/Carbon/src/Carbon/Lang/es_BO.php new file mode 100644 index 00000000000..6ba07ad068f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_BO.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_BR.php b/libraries/Carbon/src/Carbon/Lang/es_BR.php new file mode 100644 index 00000000000..46322defbf1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_BR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_BZ.php b/libraries/Carbon/src/Carbon/Lang/es_BZ.php new file mode 100644 index 00000000000..46322defbf1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_BZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_CL.php b/libraries/Carbon/src/Carbon/Lang/es_CL.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_CL.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_CO.php b/libraries/Carbon/src/Carbon/Lang/es_CO.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_CO.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_CR.php b/libraries/Carbon/src/Carbon/Lang/es_CR.php new file mode 100644 index 00000000000..16d9db74fcd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_CR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_CU.php b/libraries/Carbon/src/Carbon/Lang/es_CU.php new file mode 100644 index 00000000000..57c1f414eb4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_CU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_DO.php b/libraries/Carbon/src/Carbon/Lang/es_DO.php new file mode 100644 index 00000000000..35b3eba9089 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_DO.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - kostas + * - François B + * - Tim Fish + * - Chiel Robben + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'anteayer', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'LLL' => 'D [de] MMMM [de] YYYY h:mm A', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm A', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_EA.php b/libraries/Carbon/src/Carbon/Lang/es_EA.php new file mode 100644 index 00000000000..57c1f414eb4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_EA.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_EC.php b/libraries/Carbon/src/Carbon/Lang/es_EC.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_EC.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_ES.php b/libraries/Carbon/src/Carbon/Lang/es_ES.php new file mode 100644 index 00000000000..fac7500e1f6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_ES.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return require __DIR__.'/es.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/es_GQ.php b/libraries/Carbon/src/Carbon/Lang/es_GQ.php new file mode 100644 index 00000000000..57c1f414eb4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_GQ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_GT.php b/libraries/Carbon/src/Carbon/Lang/es_GT.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_GT.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_HN.php b/libraries/Carbon/src/Carbon/Lang/es_HN.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_HN.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_IC.php b/libraries/Carbon/src/Carbon/Lang/es_IC.php new file mode 100644 index 00000000000..57c1f414eb4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_IC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_MX.php b/libraries/Carbon/src/Carbon/Lang/es_MX.php new file mode 100644 index 00000000000..0273ed49a67 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_MX.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'antier', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_NI.php b/libraries/Carbon/src/Carbon/Lang/es_NI.php new file mode 100644 index 00000000000..2808e07a0f8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_NI.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_PA.php b/libraries/Carbon/src/Carbon/Lang/es_PA.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_PA.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_PE.php b/libraries/Carbon/src/Carbon/Lang/es_PE.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_PE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_PH.php b/libraries/Carbon/src/Carbon/Lang/es_PH.php new file mode 100644 index 00000000000..d5b20c1f5b7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_PH.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY h:mm a', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_PR.php b/libraries/Carbon/src/Carbon/Lang/es_PR.php new file mode 100644 index 00000000000..2808e07a0f8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_PR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_PY.php b/libraries/Carbon/src/Carbon/Lang/es_PY.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_PY.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_SV.php b/libraries/Carbon/src/Carbon/Lang/es_SV.php new file mode 100644 index 00000000000..62416f42b06 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_SV.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_US.php b/libraries/Carbon/src/Carbon/Lang/es_US.php new file mode 100644 index 00000000000..879772d3c5e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_US.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - Jørn Ølmheim + * - Craig Patik + * - bustta + * - François B + * - Tim Fish + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'anteayer', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'MM/DD/YYYY', + 'LL' => 'MMMM [de] D [de] YYYY', + 'LLL' => 'MMMM [de] D [de] YYYY h:mm A', + 'LLLL' => 'dddd, MMMM [de] D [de] YYYY h:mm A', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_UY.php b/libraries/Carbon/src/Carbon/Lang/es_UY.php new file mode 100644 index 00000000000..b63bb14e1c6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_UY.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'setiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'set', 'oct', 'nov', 'dic'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/es_VE.php b/libraries/Carbon/src/Carbon/Lang/es_VE.php new file mode 100644 index 00000000000..5d7b29f0b09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/es_VE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/et.php b/libraries/Carbon/src/Carbon/Lang/et.php new file mode 100644 index 00000000000..96378e72160 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/et.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Andres Ivanov + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - RM87 + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Esko Lehtme + * - Mart Karu + * - Nicolás Hock Isaza + * - Kevin Valdek + * - Zahhar Kirillov + * - João Magalhães + * - Ingmar + * - Illimar Tambek + * - Mihkel + */ +return [ + 'year' => ':count aasta|:count aastat', + 'y' => ':count a', + 'month' => ':count kuu|:count kuud', + 'm' => ':count k', + 'week' => ':count nädal|:count nädalat', + 'w' => ':count näd', + 'day' => ':count päev|:count päeva', + 'd' => ':count p', + 'hour' => ':count tund|:count tundi', + 'h' => ':count t', + 'minute' => ':count minut|:count minutit', + 'min' => ':count min', + 'second' => ':count sekund|:count sekundit', + 's' => ':count s', + 'ago' => ':time tagasi', + 'from_now' => ':time pärast', + 'after' => ':time pärast', + 'before' => ':time enne', + 'year_from_now' => ':count aasta', + 'month_from_now' => ':count kuu', + 'week_from_now' => ':count nädala', + 'day_from_now' => ':count päeva', + 'hour_from_now' => ':count tunni', + 'minute_from_now' => ':count minuti', + 'second_from_now' => ':count sekundi', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'diff_now' => 'nüüd', + 'diff_today' => 'täna', + 'diff_yesterday' => 'eile', + 'diff_tomorrow' => 'homme', + 'diff_before_yesterday' => 'üleeile', + 'diff_after_tomorrow' => 'ülehomme', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[täna] LT', + 'nextDay' => '[homme] LT', + 'lastDay' => '[eile] LT', + 'nextWeek' => 'dddd LT', + 'lastWeek' => '[eelmine] dddd LT', + 'sameElse' => 'L', + ], + 'months' => ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], + 'months_short' => ['jaan', 'veebr', 'märts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], + 'weekdays' => ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'], + 'weekdays_short' => ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + 'weekdays_min' => ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + 'list' => [', ', ' ja '], + 'meridiem' => ['enne lõunat', 'pärast lõunat'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/et_EE.php b/libraries/Carbon/src/Carbon/Lang/et_EE.php new file mode 100644 index 00000000000..95fd33ef3ed --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/et_EE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/et.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/eu.php b/libraries/Carbon/src/Carbon/Lang/eu.php new file mode 100644 index 00000000000..b90ce977341 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/eu.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + */ +return [ + 'year' => 'urte bat|:count urte', + 'y' => 'Urte 1|:count urte', + 'month' => 'hilabete bat|:count hilabete', + 'm' => 'Hile 1|:count hile', + 'week' => 'Aste 1|:count aste', + 'w' => 'Aste 1|:count aste', + 'day' => 'egun bat|:count egun', + 'd' => 'Egun 1|:count egun', + 'hour' => 'ordu bat|:count ordu', + 'h' => 'Ordu 1|:count ordu', + 'minute' => 'minutu bat|:count minutu', + 'min' => 'Minutu 1|:count minutu', + 'second' => 'segundo batzuk|:count segundo', + 's' => 'Segundu 1|:count segundu', + 'ago' => 'duela :time', + 'from_now' => ':time barru', + 'after' => ':time geroago', + 'before' => ':time lehenago', + 'diff_now' => 'orain', + 'diff_today' => 'gaur', + 'diff_yesterday' => 'atzo', + 'diff_tomorrow' => 'bihar', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY[ko] MMMM[ren] D[a]', + 'LLL' => 'YYYY[ko] MMMM[ren] D[a] HH:mm', + 'LLLL' => 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[gaur] LT[etan]', + 'nextDay' => '[bihar] LT[etan]', + 'nextWeek' => 'dddd LT[etan]', + 'lastDay' => '[atzo] LT[etan]', + 'lastWeek' => '[aurreko] dddd LT[etan]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['urtarrila', 'otsaila', 'martxoa', 'apirila', 'maiatza', 'ekaina', 'uztaila', 'abuztua', 'iraila', 'urria', 'azaroa', 'abendua'], + 'months_short' => ['urt.', 'ots.', 'mar.', 'api.', 'mai.', 'eka.', 'uzt.', 'abu.', 'ira.', 'urr.', 'aza.', 'abe.'], + 'weekdays' => ['igandea', 'astelehena', 'asteartea', 'asteazkena', 'osteguna', 'ostirala', 'larunbata'], + 'weekdays_short' => ['ig.', 'al.', 'ar.', 'az.', 'og.', 'ol.', 'lr.'], + 'weekdays_min' => ['ig', 'al', 'ar', 'az', 'og', 'ol', 'lr'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' eta '], + 'meridiem' => ['g', 'a'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/eu_ES.php b/libraries/Carbon/src/Carbon/Lang/eu_ES.php new file mode 100644 index 00000000000..325353151d5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/eu_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/eu.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ewo.php b/libraries/Carbon/src/Carbon/Lang/ewo.php new file mode 100644 index 00000000000..41422d35514 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ewo.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kíkíríg', 'ngəgógəle'], + 'weekdays' => ['sɔ́ndɔ', 'mɔ́ndi', 'sɔ́ndɔ məlú mə́bɛ̌', 'sɔ́ndɔ məlú mə́lɛ́', 'sɔ́ndɔ məlú mə́nyi', 'fúladé', 'séradé'], + 'weekdays_short' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'fúl', 'sér'], + 'weekdays_min' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'fúl', 'sér'], + 'months' => ['ngɔn osú', 'ngɔn bɛ̌', 'ngɔn lála', 'ngɔn nyina', 'ngɔn tána', 'ngɔn saməna', 'ngɔn zamgbála', 'ngɔn mwom', 'ngɔn ebulú', 'ngɔn awóm', 'ngɔn awóm ai dziá', 'ngɔn awóm ai bɛ̌'], + 'months_short' => ['ngo', 'ngb', 'ngl', 'ngn', 'ngt', 'ngs', 'ngz', 'ngm', 'nge', 'nga', 'ngad', 'ngab'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count mbu', // less reliable + 'y' => ':count mbu', // less reliable + 'a_year' => ':count mbu', // less reliable + + 'month' => ':count ngòn', // less reliable + 'm' => ':count ngòn', // less reliable + 'a_month' => ':count ngòn', // less reliable + + 'week' => ':count mësë', // less reliable + 'w' => ':count mësë', // less reliable + 'a_week' => ':count mësë', // less reliable + + 'day' => ':count mësë', // less reliable + 'd' => ':count mësë', // less reliable + 'a_day' => ':count mësë', // less reliable + + 'hour' => ':count awola', // less reliable + 'h' => ':count awola', // less reliable + 'a_hour' => ':count awola', // less reliable + + 'minute' => ':count awola', // less reliable + 'min' => ':count awola', // less reliable + 'a_minute' => ':count awola', // less reliable + */ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fa.php b/libraries/Carbon/src/Carbon/Lang/fa.php new file mode 100644 index 00000000000..1934a62d5e6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fa.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Nasser Ghiasi + * - JD Isaacks + * - Hossein Jabbari + * - nimamo + * - hafezdivandari + * - Hassan Pezeshk (hpez) + */ +return [ + 'year' => ':count سال', + 'a_year' => 'یک سال'.'|:count '.'سال', + 'y' => ':count سال', + 'month' => ':count ماه', + 'a_month' => 'یک ماه'.'|:count '.'ماه', + 'm' => ':count ماه', + 'week' => ':count هفته', + 'a_week' => 'یک هفته'.'|:count '.'هفته', + 'w' => ':count هفته', + 'day' => ':count روز', + 'a_day' => 'یک روز'.'|:count '.'روز', + 'd' => ':count روز', + 'hour' => ':count ساعت', + 'a_hour' => 'یک ساعت'.'|:count '.'ساعت', + 'h' => ':count ساعت', + 'minute' => ':count دقیقه', + 'a_minute' => 'یک دقیقه'.'|:count '.'دقیقه', + 'min' => ':count دقیقه', + 'second' => ':count ثانیه', + 's' => ':count ثانیه', + 'ago' => ':time پیش', + 'from_now' => ':time دیگر', + 'after' => ':time پس از', + 'before' => ':time پیش از', + 'diff_now' => 'اکنون', + 'diff_today' => 'امروز', + 'diff_today_regexp' => 'امروز(?:\\s+ساعت)?', + 'diff_yesterday' => 'دیروز', + 'diff_yesterday_regexp' => 'دیروز(?:\\s+ساعت)?', + 'diff_tomorrow' => 'فردا', + 'diff_tomorrow_regexp' => 'فردا(?:\\s+ساعت)?', + 'formats' => [ + 'LT' => 'OH:Om', + 'LTS' => 'OH:Om:Os', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY OH:Om', + 'LLLL' => 'dddd, OD MMMM OY OH:Om', + ], + 'calendar' => [ + 'sameDay' => '[امروز ساعت] LT', + 'nextDay' => '[فردا ساعت] LT', + 'nextWeek' => 'dddd [ساعت] LT', + 'lastDay' => '[دیروز ساعت] LT', + 'lastWeek' => 'dddd [پیش] [ساعت] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':timeم', + 'meridiem' => ['قبل از ظهر', 'بعد از ظهر'], + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_short' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_min' => ['ی', 'د', 'س', 'چ', 'پ', 'ج', 'ش'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'list' => ['، ', ' و '], + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰۴', '۰۵', '۰۶', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱۴', '۱۵', '۱۶', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲۴', '۲۵', '۲۶', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳۴', '۳۵', '۳۶', '۳۷', '۳۸', '۳۹', '۴۰', '۴۱', '۴۲', '۴۳', '۴۴', '۴۵', '۴۶', '۴۷', '۴۸', '۴۹', '۵۰', '۵۱', '۵۲', '۵۳', '۵۴', '۵۵', '۵۶', '۵۷', '۵۸', '۵۹', '۶۰', '۶۱', '۶۲', '۶۳', '۶۴', '۶۵', '۶۶', '۶۷', '۶۸', '۶۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷۴', '۷۵', '۷۶', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸۴', '۸۵', '۸۶', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹۴', '۹۵', '۹۶', '۹۷', '۹۸', '۹۹'], + 'months_short_standalone' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'weekend' => [5, 5], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/fa_AF.php b/libraries/Carbon/src/Carbon/Lang/fa_AF.php new file mode 100644 index 00000000000..586f2c58ef6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fa_AF.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'meridiem' => ['ق', 'ب'], + 'weekend' => [4, 5], + 'formats' => [ + 'L' => 'OY/OM/OD', + 'LL' => 'OD MMM OY', + 'LLL' => 'OD MMMM OY،‏ H:mm', + 'LLLL' => 'dddd OD MMMM OY،‏ H:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fa_IR.php b/libraries/Carbon/src/Carbon/Lang/fa_IR.php new file mode 100644 index 00000000000..ef3399fc7b0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fa_IR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fa.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ff.php b/libraries/Carbon/src/Carbon/Lang/ff.php new file mode 100644 index 00000000000..d5557dd24b3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ff.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['siilo', 'colte', 'mbooy', 'seeɗto', 'duujal', 'korse', 'morso', 'juko', 'siilto', 'yarkomaa', 'jolal', 'bowte'], + 'months_short' => ['sii', 'col', 'mbo', 'see', 'duu', 'kor', 'mor', 'juk', 'slt', 'yar', 'jol', 'bow'], + 'weekdays' => ['dewo', 'aaɓnde', 'mawbaare', 'njeslaare', 'naasaande', 'mawnde', 'hoore-biir'], + 'weekdays_short' => ['dew', 'aaɓ', 'maw', 'nje', 'naa', 'mwd', 'hbi'], + 'weekdays_min' => ['dew', 'aaɓ', 'maw', 'nje', 'naa', 'mwd', 'hbi'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['subaka', 'kikiiɗe'], + + 'year' => ':count baret', // less reliable + 'y' => ':count baret', // less reliable + 'a_year' => ':count baret', // less reliable + + 'month' => ':count lewru', // less reliable + 'm' => ':count lewru', // less reliable + 'a_month' => ':count lewru', // less reliable + + 'week' => ':count naange', // less reliable + 'w' => ':count naange', // less reliable + 'a_week' => ':count naange', // less reliable + + 'day' => ':count dian', // less reliable + 'd' => ':count dian', // less reliable + 'a_day' => ':count dian', // less reliable + + 'hour' => ':count montor', // less reliable + 'h' => ':count montor', // less reliable + 'a_hour' => ':count montor', // less reliable + + 'minute' => ':count tokossuoum', // less reliable + 'min' => ':count tokossuoum', // less reliable + 'a_minute' => ':count tokossuoum', // less reliable + + 'second' => ':count tenen', // less reliable + 's' => ':count tenen', // less reliable + 'a_second' => ':count tenen', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ff_CM.php b/libraries/Carbon/src/Carbon/Lang/ff_CM.php new file mode 100644 index 00000000000..2a7a9a60fc5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ff_CM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ff.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ff_GN.php b/libraries/Carbon/src/Carbon/Lang/ff_GN.php new file mode 100644 index 00000000000..2a7a9a60fc5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ff_GN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ff.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ff_MR.php b/libraries/Carbon/src/Carbon/Lang/ff_MR.php new file mode 100644 index 00000000000..6bdcb58ac25 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ff_MR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ff.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ff_SN.php b/libraries/Carbon/src/Carbon/Lang/ff_SN.php new file mode 100644 index 00000000000..786df55d7b3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ff_SN.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pular-Fulfulde.org Ibrahima Sarr admin@pulaar-fulfulde.org + */ +return require __DIR__.'/ff.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fi.php b/libraries/Carbon/src/Carbon/Lang/fi.php new file mode 100644 index 00000000000..424de8bf918 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fi.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Janne Warén + * - digitalfrost + * - Tsutomu Kuroda + * - Roope Salmi + * - tjku + * - Max Melentiev + * - Sami Haahtinen + * - Teemu Leisti + * - Artem Ignatyev + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Robert Bjarnason + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Tom Hughes + * - Sven Fuchs + * - Petri Kivikangas + * - Nizar Jouini + * - Marko Seppae + * - Tomi Mynttinen (Pikseli) + * - Petteri (powergrip) + */ +return [ + 'year' => ':count vuosi|:count vuotta', + 'y' => ':count v', + 'month' => ':count kuukausi|:count kuukautta', + 'm' => ':count kk', + 'week' => ':count viikko|:count viikkoa', + 'w' => ':count vk', + 'day' => ':count päivä|:count päivää', + 'd' => ':count pv', + 'hour' => ':count tunti|:count tuntia', + 'h' => ':count t', + 'minute' => ':count minuutti|:count minuuttia', + 'min' => ':count min', + 'second' => ':count sekunti|:count sekuntia', + 'a_second' => 'muutama sekunti|:count sekuntia', + 's' => ':count s', + 'ago' => ':time sitten', + 'from_now' => ':time päästä', + 'year_from_now' => ':count vuoden', + 'month_from_now' => ':count kuukauden', + 'week_from_now' => ':count viikon', + 'day_from_now' => ':count päivän', + 'hour_from_now' => ':count tunnin', + 'minute_from_now' => ':count minuutin', + 'second_from_now' => ':count sekunnin', + 'after' => ':time sen jälkeen', + 'before' => ':time ennen', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ja '], + 'diff_now' => 'nyt', + 'diff_yesterday' => 'eilen', + 'diff_tomorrow' => 'huomenna', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm:ss', + 'L' => 'D.M.YYYY', + 'LL' => 'dddd D. MMMM[ta] YYYY', + 'll' => 'ddd D. MMM YYYY', + 'LLL' => 'D.MM. HH.mm', + 'LLLL' => 'D. MMMM[ta] YYYY HH.mm', + 'llll' => 'D. MMM YY HH.mm', + ], + 'weekdays' => ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], + 'weekdays_short' => ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + 'weekdays_min' => ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + 'months' => ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesäkuu', 'heinäkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], + 'months_short' => ['tammi', 'helmi', 'maalis', 'huhti', 'touko', 'kesä', 'heinä', 'elo', 'syys', 'loka', 'marras', 'joulu'], + 'meridiem' => ['aamupäivä', 'iltapäivä'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/fi_FI.php b/libraries/Carbon/src/Carbon/Lang/fi_FI.php new file mode 100644 index 00000000000..c38f118e387 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fi_FI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fi.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fil.php b/libraries/Carbon/src/Carbon/Lang/fil.php new file mode 100644 index 00000000000..d1df366b96d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fil.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/fil_PH.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fil_PH.php b/libraries/Carbon/src/Carbon/Lang/fil_PH.php new file mode 100644 index 00000000000..a81703610cd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fil_PH.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rene Torres Rene Torres, Pablo Saratxaga rgtorre@rocketmail.com, pablo@mandrakesoft.com + * - Jaycee Mariano (alohajaycee) + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YY', + ], + 'months' => ['Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre'], + 'months_short' => ['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'], + 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkoles', 'Huwebes', 'Biyernes', 'Sabado'], + 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'weekdays_min' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['N.U.', 'N.H.'], + + 'before' => ':time bago', + 'after' => ':time pagkatapos', + + 'year' => ':count taon', + 'y' => ':count taon', + 'a_year' => ':count taon', + + 'month' => ':count buwan', + 'm' => ':count buwan', + 'a_month' => ':count buwan', + + 'week' => ':count linggo', + 'w' => ':count linggo', + 'a_week' => ':count linggo', + + 'day' => ':count araw', + 'd' => ':count araw', + 'a_day' => ':count araw', + + 'hour' => ':count oras', + 'h' => ':count oras', + 'a_hour' => ':count oras', + + 'minute' => ':count minuto', + 'min' => ':count minuto', + 'a_minute' => ':count minuto', + + 'second' => ':count segundo', + 's' => ':count segundo', + 'a_second' => ':count segundo', + + 'ago' => ':time ang nakalipas', + 'from_now' => 'sa :time', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fo.php b/libraries/Carbon/src/Carbon/Lang/fo.php new file mode 100644 index 00000000000..76742415e94 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fo.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kristian Sakarisson + * - François B + * - JD Isaacks + * - Sverri Mohr Olsen + */ +return [ + 'year' => 'eitt ár|:count ár', + 'y' => ':count ár|:count ár', + 'month' => 'ein mánaði|:count mánaðir', + 'm' => ':count mánaður|:count mánaðir', + 'week' => ':count vika|:count vikur', + 'w' => ':count vika|:count vikur', + 'day' => 'ein dagur|:count dagar', + 'd' => ':count dag|:count dagar', + 'hour' => 'ein tími|:count tímar', + 'h' => ':count tími|:count tímar', + 'minute' => 'ein minutt|:count minuttir', + 'min' => ':count minutt|:count minuttir', + 'second' => 'fá sekund|:count sekundir', + 's' => ':count sekund|:count sekundir', + 'ago' => ':time síðani', + 'from_now' => 'um :time', + 'after' => ':time aftaná', + 'before' => ':time áðrenn', + 'diff_today' => 'Í', + 'diff_yesterday' => 'Í', + 'diff_yesterday_regexp' => 'Í(?:\\s+gjár)?(?:\\s+kl.)?', + 'diff_tomorrow' => 'Í', + 'diff_tomorrow_regexp' => 'Í(?:\\s+morgin)?(?:\\s+kl.)?', + 'diff_today_regexp' => 'Í(?:\\s+dag)?(?:\\s+kl.)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D. MMMM, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Í dag kl.] LT', + 'nextDay' => '[Í morgin kl.] LT', + 'nextWeek' => 'dddd [kl.] LT', + 'lastDay' => '[Í gjár kl.] LT', + 'lastWeek' => '[síðstu] dddd [kl] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mars', 'apríl', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sunnudagur', 'mánadagur', 'týsdagur', 'mikudagur', 'hósdagur', 'fríggjadagur', 'leygardagur'], + 'weekdays_short' => ['sun', 'mán', 'týs', 'mik', 'hós', 'frí', 'ley'], + 'weekdays_min' => ['su', 'má', 'tý', 'mi', 'hó', 'fr', 'le'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/fo_DK.php b/libraries/Carbon/src/Carbon/Lang/fo_DK.php new file mode 100644 index 00000000000..c6306e97f77 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fo_DK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fo.php', [ + 'formats' => [ + 'L' => 'DD.MM.yy', + 'LL' => 'DD.MM.YYYY', + 'LLL' => 'D. MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY, HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fo_FO.php b/libraries/Carbon/src/Carbon/Lang/fo_FO.php new file mode 100644 index 00000000000..fdd792f2dfb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fo_FO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fo.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr.php b/libraries/Carbon/src/Carbon/Lang/fr.php new file mode 100644 index 00000000000..3923ef32b92 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Maxime VALY + * - JD Isaacks + * - Dieter Sting + * - François B + * - JD Isaacks + * - Sebastian Thierer + * - Fastfuel + * - Pete Scopes (pdscopes) + */ +return [ + 'year' => ':count an|:count ans', + 'a_year' => 'un an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mois|:count mois', + 'a_month' => 'un mois|:count mois', + 'm' => ':count mois', + 'week' => ':count semaine|:count semaines', + 'a_week' => 'une semaine|:count semaines', + 'w' => ':count sem.', + 'day' => ':count jour|:count jours', + 'a_day' => 'un jour|:count jours', + 'd' => ':count j', + 'hour' => ':count heure|:count heures', + 'a_hour' => 'une heure|:count heures', + 'h' => ':count h', + 'minute' => ':count minute|:count minutes', + 'a_minute' => 'une minute|:count minutes', + 'min' => ':count min', + 'second' => ':count seconde|:count secondes', + 'a_second' => 'quelques secondes|:count secondes', + 's' => ':count s', + 'millisecond' => ':count milliseconde|:count millisecondes', + 'a_millisecond' => 'une milliseconde|:count millisecondes', + 'ms' => ':countms', + 'microsecond' => ':count microseconde|:count microsecondes', + 'a_microsecond' => 'une microseconde|:count microsecondes', + 'µs' => ':countµs', + 'ago' => 'il y a :time', + 'from_now' => 'dans :time', + 'after' => ':time après', + 'before' => ':time avant', + 'diff_now' => "à l'instant", + 'diff_today' => "aujourd'hui", + 'diff_today_regexp' => "aujourd'hui(?:\s+à)?", + 'diff_yesterday' => 'hier', + 'diff_yesterday_regexp' => 'hier(?:\s+à)?', + 'diff_tomorrow' => 'demain', + 'diff_tomorrow_regexp' => 'demain(?:\s+à)?', + 'diff_before_yesterday' => 'avant-hier', + 'diff_after_tomorrow' => 'après-demain', + 'period_recurrences' => ':count fois', + 'period_interval' => 'tous les :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'à :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Aujourd’hui à] LT', + 'nextDay' => '[Demain à] LT', + 'nextWeek' => 'dddd [à] LT', + 'lastDay' => '[Hier à] LT', + 'lastWeek' => 'dddd [dernier à] LT', + 'sameElse' => 'L', + ], + 'months' => ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'], + 'months_short' => ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'], + 'weekdays' => ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'], + 'weekdays_short' => ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'je', 've', 'sa'], + 'ordinal' => function ($number, $period) { + switch ($period) { + // In French, only the first has to be ordinal, other number remains cardinal + // @link https://fr.wikihow.com/%C3%A9crire-la-date-en-fran%C3%A7ais + case 'D': + return $number.($number === 1 ? 'er' : ''); + + default: + case 'M': + case 'Q': + case 'DDD': + case 'd': + return $number.($number === 1 ? 'er' : 'e'); + + // Words with feminine grammatical gender: semaine + case 'w': + case 'W': + return $number.($number === 1 ? 're' : 'e'); + } + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' et '], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'premier', + 'second' => 'deuxième', + 'third' => 'troisième', + 'fourth' => 'quatrième', + 'fifth' => 'cinquième', + 'last' => 'dernier', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_BE.php b/libraries/Carbon/src/Carbon/Lang/fr_BE.php new file mode 100644 index 00000000000..9a953a83bbc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_BE.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'months_short' => ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoû', 'sep', 'oct', 'nov', 'déc'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_BF.php b/libraries/Carbon/src/Carbon/Lang/fr_BF.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_BF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_BI.php b/libraries/Carbon/src/Carbon/Lang/fr_BI.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_BI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_BJ.php b/libraries/Carbon/src/Carbon/Lang/fr_BJ.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_BJ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_BL.php b/libraries/Carbon/src/Carbon/Lang/fr_BL.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_BL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_CA.php b/libraries/Carbon/src/Carbon/Lang/fr_CA.php new file mode 100644 index 00000000000..99d85d0591e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_CA.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Maxime VALY + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_CD.php b/libraries/Carbon/src/Carbon/Lang/fr_CD.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_CD.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_CF.php b/libraries/Carbon/src/Carbon/Lang/fr_CF.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_CF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_CG.php b/libraries/Carbon/src/Carbon/Lang/fr_CG.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_CG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_CH.php b/libraries/Carbon/src/Carbon/Lang/fr_CH.php new file mode 100644 index 00000000000..1838269c158 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_CH.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Gaspard Bucher + * - Maxime VALY + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_CI.php b/libraries/Carbon/src/Carbon/Lang/fr_CI.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_CI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_CM.php b/libraries/Carbon/src/Carbon/Lang/fr_CM.php new file mode 100644 index 00000000000..b201bd1a104 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_CM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'meridiem' => ['mat.', 'soir'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_DJ.php b/libraries/Carbon/src/Carbon/Lang/fr_DJ.php new file mode 100644 index 00000000000..25d1b0a7378 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_DJ.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_DZ.php b/libraries/Carbon/src/Carbon/Lang/fr_DZ.php new file mode 100644 index 00000000000..b8808cd151e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_DZ.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_FR.php b/libraries/Carbon/src/Carbon/Lang/fr_FR.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_GA.php b/libraries/Carbon/src/Carbon/Lang/fr_GA.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_GA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_GF.php b/libraries/Carbon/src/Carbon/Lang/fr_GF.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_GF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_GN.php b/libraries/Carbon/src/Carbon/Lang/fr_GN.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_GN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_GP.php b/libraries/Carbon/src/Carbon/Lang/fr_GP.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_GP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_GQ.php b/libraries/Carbon/src/Carbon/Lang/fr_GQ.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_GQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_HT.php b/libraries/Carbon/src/Carbon/Lang/fr_HT.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_HT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_KM.php b/libraries/Carbon/src/Carbon/Lang/fr_KM.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_KM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_LU.php b/libraries/Carbon/src/Carbon/Lang/fr_LU.php new file mode 100644 index 00000000000..f054125855e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_LU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months_short' => ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoû', 'sep', 'oct', 'nov', 'déc'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_MA.php b/libraries/Carbon/src/Carbon/Lang/fr_MA.php new file mode 100644 index 00000000000..9447d8f97dd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_MA.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_MC.php b/libraries/Carbon/src/Carbon/Lang/fr_MC.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_MC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_MF.php b/libraries/Carbon/src/Carbon/Lang/fr_MF.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_MF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_MG.php b/libraries/Carbon/src/Carbon/Lang/fr_MG.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_MG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_ML.php b/libraries/Carbon/src/Carbon/Lang/fr_ML.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_ML.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_MQ.php b/libraries/Carbon/src/Carbon/Lang/fr_MQ.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_MQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_MR.php b/libraries/Carbon/src/Carbon/Lang/fr_MR.php new file mode 100644 index 00000000000..94466bc586f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_MR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_MU.php b/libraries/Carbon/src/Carbon/Lang/fr_MU.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_MU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_NC.php b/libraries/Carbon/src/Carbon/Lang/fr_NC.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_NC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_NE.php b/libraries/Carbon/src/Carbon/Lang/fr_NE.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_NE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_PF.php b/libraries/Carbon/src/Carbon/Lang/fr_PF.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_PF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_PM.php b/libraries/Carbon/src/Carbon/Lang/fr_PM.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_PM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_RE.php b/libraries/Carbon/src/Carbon/Lang/fr_RE.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_RE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_RW.php b/libraries/Carbon/src/Carbon/Lang/fr_RW.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_RW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_SC.php b/libraries/Carbon/src/Carbon/Lang/fr_SC.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_SC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_SN.php b/libraries/Carbon/src/Carbon/Lang/fr_SN.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_SN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_SY.php b/libraries/Carbon/src/Carbon/Lang/fr_SY.php new file mode 100644 index 00000000000..b8808cd151e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_SY.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_TD.php b/libraries/Carbon/src/Carbon/Lang/fr_TD.php new file mode 100644 index 00000000000..94466bc586f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_TD.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_TG.php b/libraries/Carbon/src/Carbon/Lang/fr_TG.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_TG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_TN.php b/libraries/Carbon/src/Carbon/Lang/fr_TN.php new file mode 100644 index 00000000000..340fe0ecd88 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_TN.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_VU.php b/libraries/Carbon/src/Carbon/Lang/fr_VU.php new file mode 100644 index 00000000000..94466bc586f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_VU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fr_WF.php b/libraries/Carbon/src/Carbon/Lang/fr_WF.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_WF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fr_YT.php b/libraries/Carbon/src/Carbon/Lang/fr_YT.php new file mode 100644 index 00000000000..ea306f54966 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fr_YT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fur.php b/libraries/Carbon/src/Carbon/Lang/fur.php new file mode 100644 index 00000000000..fec406dda55 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fur.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/fur_IT.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/fur_IT.php b/libraries/Carbon/src/Carbon/Lang/fur_IT.php new file mode 100644 index 00000000000..0fb2849d0e6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fur_IT.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD. MM. YY', + 'LL' => 'DD di MMMM dal YYYY', + 'LLL' => 'DD di MMM HH:mm', + 'LLLL' => 'DD di MMMM dal YYYY HH:mm', + ], + 'months' => ['zenâr', 'fevrâr', 'març', 'avrîl', 'mai', 'jugn', 'lui', 'avost', 'setembar', 'otubar', 'novembar', 'dicembar'], + 'months_short' => ['zen', 'fev', 'mar', 'avr', 'mai', 'jug', 'lui', 'avo', 'set', 'otu', 'nov', 'dic'], + 'weekdays' => ['domenie', 'lunis', 'martars', 'miercus', 'joibe', 'vinars', 'sabide'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mie', 'joi', 'vin', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mie', 'joi', 'vin', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'year' => ':count an', + 'month' => ':count mês', + 'week' => ':count setemane', + 'day' => ':count zornade', + 'hour' => ':count ore', + 'minute' => ':count minût', + 'second' => ':count secont', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/fy.php b/libraries/Carbon/src/Carbon/Lang/fy.php new file mode 100644 index 00000000000..1501b5f1cf1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fy.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Tim Fish + * - JD Isaacks + */ +return [ + 'year' => ':count jier|:count jierren', + 'a_year' => 'ien jier|:count jierren', + 'y' => ':count j', + 'month' => ':count moanne|:count moannen', + 'a_month' => 'ien moanne|:count moannen', + 'm' => ':count moa.', + 'week' => ':count wike|:count wiken', + 'a_week' => 'in wike|:count wiken', + 'a' => ':count w.', + 'day' => ':count dei|:count dagen', + 'a_day' => 'ien dei|:count dagen', + 'd' => ':count d.', + 'hour' => ':count oere|:count oeren', + 'a_hour' => 'ien oere|:count oeren', + 'h' => ':count o.', + 'minute' => ':count minút|:count minuten', + 'a_minute' => 'ien minút|:count minuten', + 'min' => ':count min.', + 'second' => ':count sekonde|:count sekonden', + 'a_second' => 'in pear sekonden|:count sekonden', + 's' => ':count s.', + 'ago' => ':time lyn', + 'from_now' => 'oer :time', + 'diff_yesterday' => 'juster', + 'diff_yesterday_regexp' => 'juster(?:\\s+om)?', + 'diff_today' => 'hjoed', + 'diff_today_regexp' => 'hjoed(?:\\s+om)?', + 'diff_tomorrow' => 'moarn', + 'diff_tomorrow_regexp' => 'moarn(?:\\s+om)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[hjoed om] LT', + 'nextDay' => '[moarn om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[juster om] LT', + 'lastWeek' => '[ôfrûne] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'months' => ['jannewaris', 'febrewaris', 'maart', 'april', 'maaie', 'juny', 'july', 'augustus', 'septimber', 'oktober', 'novimber', 'desimber'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'mmm_suffix' => '.', + 'weekdays' => ['snein', 'moandei', 'tiisdei', 'woansdei', 'tongersdei', 'freed', 'sneon'], + 'weekdays_short' => ['si.', 'mo.', 'ti.', 'wo.', 'to.', 'fr.', 'so.'], + 'weekdays_min' => ['Si', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/fy_DE.php b/libraries/Carbon/src/Carbon/Lang/fy_DE.php new file mode 100644 index 00000000000..0e7c190775f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fy_DE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jaunuwoa', 'Februwoa', 'Moaz', 'Aprell', 'Mai', 'Juni', 'Juli', 'August', 'Septamba', 'Oktoba', 'Nowamba', 'Dezamba'], + 'months_short' => ['Jan', 'Feb', 'Moz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Now', 'Dez'], + 'weekdays' => ['Sinndag', 'Mondag', 'Dingsdag', 'Meddwäakj', 'Donnadag', 'Friedag', 'Sinnowend'], + 'weekdays_short' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'weekdays_min' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/fy_NL.php b/libraries/Carbon/src/Carbon/Lang/fy_NL.php new file mode 100644 index 00000000000..c2e6c78db96 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/fy_NL.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fy.php', [ + 'formats' => [ + 'L' => 'DD-MM-YY', + ], + 'months' => ['Jannewaris', 'Febrewaris', 'Maart', 'April', 'Maaie', 'Juny', 'July', 'Augustus', 'Septimber', 'Oktober', 'Novimber', 'Desimber'], + 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Snein', 'Moandei', 'Tiisdei', 'Woansdei', 'Tongersdei', 'Freed', 'Sneon'], + 'weekdays_short' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], + 'weekdays_min' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ga.php b/libraries/Carbon/src/Carbon/Lang/ga.php new file mode 100644 index 00000000000..d2ba43ecd36 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ga.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Thanks to André Silva : https://github.com/askpt + */ + +return [ + 'year' => ':count bliain', + 'a_year' => '{1}bliain|:count bliain', + 'y' => ':countb', + 'month' => ':count mí', + 'a_month' => '{1}mí|:count mí', + 'm' => ':countm', + 'week' => ':count sheachtain', + 'a_week' => '{1}sheachtain|:count sheachtain', + 'w' => ':countsh', + 'day' => ':count lá', + 'a_day' => '{1}lá|:count lá', + 'd' => ':countl', + 'hour' => ':count uair an chloig', + 'a_hour' => '{1}uair an chloig|:count uair an chloig', + 'h' => ':countu', + 'minute' => ':count nóiméad', + 'a_minute' => '{1}nóiméad|:count nóiméad', + 'min' => ':countn', + 'second' => ':count soicind', + 'a_second' => '{1}cúpla soicind|:count soicind', + 's' => ':countso', + 'ago' => ':time ó shin', + 'from_now' => 'i :time', + 'after' => ':time tar éis', + 'before' => ':time roimh', + 'diff_now' => 'anois', + 'diff_today' => 'Inniu', + 'diff_today_regexp' => 'Inniu(?:\\s+ag)?', + 'diff_yesterday' => 'inné', + 'diff_yesterday_regexp' => 'Inné(?:\\s+aig)?', + 'diff_tomorrow' => 'amárach', + 'diff_tomorrow_regexp' => 'Amárach(?:\\s+ag)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Inniu ag] LT', + 'nextDay' => '[Amárach ag] LT', + 'nextWeek' => 'dddd [ag] LT', + 'lastDay' => '[Inné aig] LT', + 'lastWeek' => 'dddd [seo caite] [ag] LT', + 'sameElse' => 'L', + ], + 'months' => ['Eanáir', 'Feabhra', 'Márta', 'Aibreán', 'Bealtaine', 'Méitheamh', 'Iúil', 'Lúnasa', 'Meán Fómhair', 'Deaireadh Fómhair', 'Samhain', 'Nollaig'], + 'months_short' => ['Eaná', 'Feab', 'Márt', 'Aibr', 'Beal', 'Méit', 'Iúil', 'Lúna', 'Meán', 'Deai', 'Samh', 'Noll'], + 'weekdays' => ['Dé Domhnaigh', 'Dé Luain', 'Dé Máirt', 'Dé Céadaoin', 'Déardaoin', 'Dé hAoine', 'Dé Satharn'], + 'weekdays_short' => ['Dom', 'Lua', 'Mái', 'Céa', 'Déa', 'hAo', 'Sat'], + 'weekdays_min' => ['Do', 'Lu', 'Má', 'Ce', 'Dé', 'hA', 'Sa'], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' agus '], + 'meridiem' => ['r.n.', 'i.n.'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ga_IE.php b/libraries/Carbon/src/Carbon/Lang/ga_IE.php new file mode 100644 index 00000000000..4c31d983120 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ga_IE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ga.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/gd.php b/libraries/Carbon/src/Carbon/Lang/gd.php new file mode 100644 index 00000000000..b4bd4dc2d54 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gd.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Jon Ashdown + */ +return [ + 'year' => ':count bliadhna', + 'a_year' => '{1}bliadhna|:count bliadhna', + 'y' => ':count b.', + 'month' => ':count mìosan', + 'a_month' => '{1}mìos|:count mìosan', + 'm' => ':count ms.', + 'week' => ':count seachdainean', + 'a_week' => '{1}seachdain|:count seachdainean', + 'w' => ':count s.', + 'day' => ':count latha', + 'a_day' => '{1}latha|:count latha', + 'd' => ':count l.', + 'hour' => ':count uairean', + 'a_hour' => '{1}uair|:count uairean', + 'h' => ':count u.', + 'minute' => ':count mionaidean', + 'a_minute' => '{1}mionaid|:count mionaidean', + 'min' => ':count md.', + 'second' => ':count diogan', + 'a_second' => '{1}beagan diogan|:count diogan', + 's' => ':count d.', + 'ago' => 'bho chionn :time', + 'from_now' => 'ann an :time', + 'diff_yesterday' => 'An-dè', + 'diff_yesterday_regexp' => 'An-dè(?:\\s+aig)?', + 'diff_today' => 'An-diugh', + 'diff_today_regexp' => 'An-diugh(?:\\s+aig)?', + 'diff_tomorrow' => 'A-màireach', + 'diff_tomorrow_regexp' => 'A-màireach(?:\\s+aig)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[An-diugh aig] LT', + 'nextDay' => '[A-màireach aig] LT', + 'nextWeek' => 'dddd [aig] LT', + 'lastDay' => '[An-dè aig] LT', + 'lastWeek' => 'dddd [seo chaidh] [aig] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); + }, + 'months' => ['Am Faoilleach', 'An Gearran', 'Am Màrt', 'An Giblean', 'An Cèitean', 'An t-Ògmhios', 'An t-Iuchar', 'An Lùnastal', 'An t-Sultain', 'An Dàmhair', 'An t-Samhain', 'An Dùbhlachd'], + 'months_short' => ['Faoi', 'Gear', 'Màrt', 'Gibl', 'Cèit', 'Ògmh', 'Iuch', 'Lùn', 'Sult', 'Dàmh', 'Samh', 'Dùbh'], + 'weekdays' => ['Didòmhnaich', 'Diluain', 'Dimàirt', 'Diciadain', 'Diardaoin', 'Dihaoine', 'Disathairne'], + 'weekdays_short' => ['Did', 'Dil', 'Dim', 'Dic', 'Dia', 'Dih', 'Dis'], + 'weekdays_min' => ['Dò', 'Lu', 'Mà', 'Ci', 'Ar', 'Ha', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' agus '], + 'meridiem' => ['m', 'f'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/gd_GB.php b/libraries/Carbon/src/Carbon/Lang/gd_GB.php new file mode 100644 index 00000000000..afbbb4f01b5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gd_GB.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gd.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/gez.php b/libraries/Carbon/src/Carbon/Lang/gez.php new file mode 100644 index 00000000000..c22a2957740 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gez.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gez_ER.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/gez_ER.php b/libraries/Carbon/src/Carbon/Lang/gez_ER.php new file mode 100644 index 00000000000..6a9ad22f4e3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gez_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጠሐረ', 'ከተተ', 'መገበ', 'አኀዘ', 'ግንባት', 'ሠንየ', 'ሐመለ', 'ነሐሰ', 'ከረመ', 'ጠቀመ', 'ኀደረ', 'ኀሠሠ'], + 'months_short' => ['ጠሐረ', 'ከተተ', 'መገበ', 'አኀዘ', 'ግንባ', 'ሠንየ', 'ሐመለ', 'ነሐሰ', 'ከረመ', 'ጠቀመ', 'ኀደረ', 'ኀሠሠ'], + 'weekdays' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚት'], + 'weekdays_short' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'weekdays_min' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጽባሕ', 'ምሴት'], + + 'month' => ':count ወርሕ', // less reliable + 'm' => ':count ወርሕ', // less reliable + 'a_month' => ':count ወርሕ', // less reliable + + 'week' => ':count ሰብዑ', // less reliable + 'w' => ':count ሰብዑ', // less reliable + 'a_week' => ':count ሰብዑ', // less reliable + + 'hour' => ':count አንትሙ', // less reliable + 'h' => ':count አንትሙ', // less reliable + 'a_hour' => ':count አንትሙ', // less reliable + + 'minute' => ':count ንኡስ', // less reliable + 'min' => ':count ንኡስ', // less reliable + 'a_minute' => ':count ንኡስ', // less reliable + + 'year' => ':count ዓመት', + 'y' => ':count ዓመት', + 'a_year' => ':count ዓመት', + + 'day' => ':count ዕለት', + 'd' => ':count ዕለት', + 'a_day' => ':count ዕለት', + + 'second' => ':count ካልእ', + 's' => ':count ካልእ', + 'a_second' => ':count ካልእ', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/gez_ET.php b/libraries/Carbon/src/Carbon/Lang/gez_ET.php new file mode 100644 index 00000000000..68a3174cc87 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gez_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚት'], + 'weekdays_short' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'weekdays_min' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጽባሕ', 'ምሴት'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/gl.php b/libraries/Carbon/src/Carbon/Lang/gl.php new file mode 100644 index 00000000000..404818df31f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gl.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Fidel Pita + * - JD Isaacks + * - Diego Vilariño + * - Sebastian Thierer + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count ano|:count anos', + 'a_year' => 'un ano|:count anos', + 'y' => ':count a.', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes.', + 'week' => ':count semana|:count semanas', + 'a_week' => 'unha semana|:count semanas', + 'w' => ':count sem.', + 'day' => ':count día|:count días', + 'a_day' => 'un día|:count días', + 'd' => ':count d.', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'unha hora|:count horas', + 'h' => ':count h.', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'un minuto|:count minutos', + 'min' => ':count min.', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'uns segundos|:count segundos', + 's' => ':count seg.', + 'ago' => 'hai :time', + 'from_now' => function ($time) { + if (str_starts_with($time, 'un')) { + return "n$time"; + } + + return "en $time"; + }, + 'diff_now' => 'agora', + 'diff_today' => 'hoxe', + 'diff_today_regexp' => 'hoxe(?:\\s+ás)?', + 'diff_yesterday' => 'onte', + 'diff_yesterday_regexp' => 'onte(?:\\s+á)?', + 'diff_tomorrow' => 'mañá', + 'diff_tomorrow_regexp' => 'mañá(?:\\s+ás)?', + 'after' => ':time despois', + 'before' => ':time antes', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY H:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[hoxe '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[mañá '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd ['.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[onte '.($current->hour !== 1 ? 'á' : 'a').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[o] dddd [pasado '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['xaneiro', 'febreiro', 'marzo', 'abril', 'maio', 'xuño', 'xullo', 'agosto', 'setembro', 'outubro', 'novembro', 'decembro'], + 'months_short' => ['xan.', 'feb.', 'mar.', 'abr.', 'mai.', 'xuñ.', 'xul.', 'ago.', 'set.', 'out.', 'nov.', 'dec.'], + 'weekdays' => ['domingo', 'luns', 'martes', 'mércores', 'xoves', 'venres', 'sábado'], + 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'mér.', 'xov.', 'ven.', 'sáb.'], + 'weekdays_min' => ['do', 'lu', 'ma', 'mé', 'xo', 've', 'sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/gl_ES.php b/libraries/Carbon/src/Carbon/Lang/gl_ES.php new file mode 100644 index 00000000000..b4de944de6e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gl_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/gom.php b/libraries/Carbon/src/Carbon/Lang/gom.php new file mode 100644 index 00000000000..81dae264506 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gom.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gom_Latn.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/gom_Latn.php b/libraries/Carbon/src/Carbon/Lang/gom_Latn.php new file mode 100644 index 00000000000..1cc1378edb5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gom_Latn.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => ':count voros|:count vorsam', + 'y' => ':countv', + 'month' => ':count mhoino|:count mhoine', + 'm' => ':countmh', + 'week' => ':count satolleacho|:count satolleache', + 'w' => ':countsa|:countsa', + 'day' => ':count dis', + 'd' => ':countd', + 'hour' => ':count hor|:count horam', + 'h' => ':counth', + 'minute' => ':count minute|:count mintam', + 'min' => ':countm', + 'second' => ':count second', + 's' => ':counts', + + 'diff_today' => 'Aiz', + 'diff_yesterday' => 'Kal', + 'diff_tomorrow' => 'Faleam', + 'formats' => [ + 'LT' => 'A h:mm [vazta]', + 'LTS' => 'A h:mm:ss [vazta]', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY A h:mm [vazta]', + 'LLLL' => 'dddd, MMMM[achea] Do, YYYY, A h:mm [vazta]', + 'llll' => 'ddd, D MMM YYYY, A h:mm [vazta]', + ], + + 'calendar' => [ + 'sameDay' => '[Aiz] LT', + 'nextDay' => '[Faleam] LT', + 'nextWeek' => '[Ieta to] dddd[,] LT', + 'lastDay' => '[Kal] LT', + 'lastWeek' => '[Fatlo] dddd[,] LT', + 'sameElse' => 'L', + ], + + 'months' => ['Janer', 'Febrer', 'Mars', 'Abril', 'Mai', 'Jun', 'Julai', 'Agost', 'Setembr', 'Otubr', 'Novembr', 'Dezembr'], + 'months_short' => ['Jan.', 'Feb.', 'Mars', 'Abr.', 'Mai', 'Jun', 'Jul.', 'Ago.', 'Set.', 'Otu.', 'Nov.', 'Dez.'], + 'weekdays' => ['Aitar', 'Somar', 'Mongllar', 'Budvar', 'Brestar', 'Sukrar', 'Son\'var'], + 'weekdays_short' => ['Ait.', 'Som.', 'Mon.', 'Bud.', 'Bre.', 'Suk.', 'Son.'], + 'weekdays_min' => ['Ai', 'Sm', 'Mo', 'Bu', 'Br', 'Su', 'Sn'], + + 'ordinal' => function ($number, $period) { + return $number.($period === 'D' ? 'er' : ''); + }, + + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'rati'; + } + if ($hour < 12) { + return 'sokalli'; + } + if ($hour < 16) { + return 'donparam'; + } + if ($hour < 20) { + return 'sanje'; + } + + return 'rati'; + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ani '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/gsw.php b/libraries/Carbon/src/Carbon/Lang/gsw.php new file mode 100644 index 00000000000..aa9eea5a54f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gsw.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Christopher Dell + * - Akira Matsuda + * - Enrique Vidal + * - Simone Carletti + * - Henning Kiel + * - Aaron Patterson + * - Florian Hanke + */ +return [ + 'year' => ':count Johr', + 'month' => ':count Monet', + 'week' => ':count Woche', + 'day' => ':count Tag', + 'hour' => ':count Schtund', + 'minute' => ':count Minute', + 'second' => ':count Sekunde', + 'weekdays' => ['Sunntig', 'Mäntig', 'Ziischtig', 'Mittwuch', 'Dunschtig', 'Friitig', 'Samschtig'], + 'weekdays_short' => ['Su', 'Mä', 'Zi', 'Mi', 'Du', 'Fr', 'Sa'], + 'weekdays_min' => ['Su', 'Mä', 'Zi', 'Mi', 'Du', 'Fr', 'Sa'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'meridiem' => ['am Vormittag', 'am Namittag'], + 'ordinal' => ':number.', + 'list' => [', ', ' und '], + 'diff_now' => 'now', + 'diff_yesterday' => 'geschter', + 'diff_tomorrow' => 'moorn', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'Do MMMM YYYY', + 'LLL' => 'Do MMMM, HH:mm [Uhr]', + 'LLLL' => 'dddd, Do MMMM YYYY, HH:mm [Uhr]', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/gsw_CH.php b/libraries/Carbon/src/Carbon/Lang/gsw_CH.php new file mode 100644 index 00000000000..5f50e8d45ed --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gsw_CH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gsw.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/gsw_FR.php b/libraries/Carbon/src/Carbon/Lang/gsw_FR.php new file mode 100644 index 00000000000..ea598186e04 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gsw_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/gsw.php', [ + 'meridiem' => ['vorm.', 'nam.'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'Septämber', 'Oktoober', 'Novämber', 'Dezämber'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LLL' => 'Do MMMM YYYY HH:mm', + 'LLLL' => 'dddd, Do MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/gsw_LI.php b/libraries/Carbon/src/Carbon/Lang/gsw_LI.php new file mode 100644 index 00000000000..ea598186e04 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gsw_LI.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/gsw.php', [ + 'meridiem' => ['vorm.', 'nam.'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'Septämber', 'Oktoober', 'Novämber', 'Dezämber'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LLL' => 'Do MMMM YYYY HH:mm', + 'LLLL' => 'dddd, Do MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/gu.php b/libraries/Carbon/src/Carbon/Lang/gu.php new file mode 100644 index 00000000000..91c420330c8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gu.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Kaushik Thanki + * - Josh Soref + */ +return [ + 'year' => 'એક વર્ષ|:count વર્ષ', + 'y' => ':countવર્ષ|:countવર્ષો', + 'month' => 'એક મહિનો|:count મહિના', + 'm' => ':countમહિનો|:countમહિના', + 'week' => ':count અઠવાડિયું|:count અઠવાડિયા', + 'w' => ':countઅઠ.|:countઅઠ.', + 'day' => 'એક દિવસ|:count દિવસ', + 'd' => ':countદિ.|:countદિ.', + 'hour' => 'એક કલાક|:count કલાક', + 'h' => ':countક.|:countક.', + 'minute' => 'એક મિનિટ|:count મિનિટ', + 'min' => ':countમિ.|:countમિ.', + 'second' => 'અમુક પળો|:count સેકંડ', + 's' => ':countસે.|:countસે.', + 'ago' => ':time પેહલા', + 'from_now' => ':time મા', + 'after' => ':time પછી', + 'before' => ':time પહેલા', + 'diff_now' => 'હમણાં', + 'diff_today' => 'આજ', + 'diff_yesterday' => 'ગઇકાલે', + 'diff_tomorrow' => 'કાલે', + 'formats' => [ + 'LT' => 'A h:mm વાગ્યે', + 'LTS' => 'A h:mm:ss વાગ્યે', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm વાગ્યે', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm વાગ્યે', + ], + 'calendar' => [ + 'sameDay' => '[આજ] LT', + 'nextDay' => '[કાલે] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ગઇકાલે] LT', + 'lastWeek' => '[પાછલા] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'રાત'; + } + if ($hour < 10) { + return 'સવાર'; + } + if ($hour < 17) { + return 'બપોર'; + } + if ($hour < 20) { + return 'સાંજ'; + } + + return 'રાત'; + }, + 'months' => ['જાન્યુઆરી', 'ફેબ્રુઆરી', 'માર્ચ', 'એપ્રિલ', 'મે', 'જૂન', 'જુલાઈ', 'ઑગસ્ટ', 'સપ્ટેમ્બર', 'ઑક્ટ્બર', 'નવેમ્બર', 'ડિસેમ્બર'], + 'months_short' => ['જાન્યુ.', 'ફેબ્રુ.', 'માર્ચ', 'એપ્રિ.', 'મે', 'જૂન', 'જુલા.', 'ઑગ.', 'સપ્ટે.', 'ઑક્ટ્.', 'નવે.', 'ડિસે.'], + 'weekdays' => ['રવિવાર', 'સોમવાર', 'મંગળવાર', 'બુધ્વાર', 'ગુરુવાર', 'શુક્રવાર', 'શનિવાર'], + 'weekdays_short' => ['રવિ', 'સોમ', 'મંગળ', 'બુધ્', 'ગુરુ', 'શુક્ર', 'શનિ'], + 'weekdays_min' => ['ર', 'સો', 'મં', 'બુ', 'ગુ', 'શુ', 'શ'], + 'list' => [', ', ' અને '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/gu_IN.php b/libraries/Carbon/src/Carbon/Lang/gu_IN.php new file mode 100644 index 00000000000..21439bbab81 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gu_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gu.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/guz.php b/libraries/Carbon/src/Carbon/Lang/guz.php new file mode 100644 index 00000000000..3d6e023608b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/guz.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ma', 'Mo'], + 'weekdays' => ['Chumapiri', 'Chumatato', 'Chumaine', 'Chumatano', 'Aramisi', 'Ichuma', 'Esabato'], + 'weekdays_short' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], + 'weekdays_min' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], + 'months' => ['Chanuari', 'Feburari', 'Machi', 'Apiriri', 'Mei', 'Juni', 'Chulai', 'Agosti', 'Septemba', 'Okitoba', 'Nobemba', 'Disemba'], + 'months_short' => ['Can', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Cul', 'Agt', 'Sep', 'Okt', 'Nob', 'Dis'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'month' => ':count omotunyi', // less reliable + 'm' => ':count omotunyi', // less reliable + 'a_month' => ':count omotunyi', // less reliable + + 'week' => ':count isano naibere', // less reliable + 'w' => ':count isano naibere', // less reliable + 'a_week' => ':count isano naibere', // less reliable + + 'second' => ':count ibere', // less reliable + 's' => ':count ibere', // less reliable + 'a_second' => ':count ibere', // less reliable + + 'year' => ':count omwaka', + 'y' => ':count omwaka', + 'a_year' => ':count omwaka', + + 'day' => ':count rituko', + 'd' => ':count rituko', + 'a_day' => ':count rituko', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/gv.php b/libraries/Carbon/src/Carbon/Lang/gv.php new file mode 100644 index 00000000000..6a65417a40e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gv.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gv_GB.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/gv_GB.php b/libraries/Carbon/src/Carbon/Lang/gv_GB.php new file mode 100644 index 00000000000..2380e59338a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/gv_GB.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alastair McKinstry bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Jerrey-geuree', 'Toshiaght-arree', 'Mayrnt', 'Averil', 'Boaldyn', 'Mean-souree', 'Jerrey-souree', 'Luanistyn', 'Mean-fouyir', 'Jerrey-fouyir', 'Mee Houney', 'Mee ny Nollick'], + 'months_short' => ['J-guer', 'T-arree', 'Mayrnt', 'Avrril', 'Boaldyn', 'M-souree', 'J-souree', 'Luanistyn', 'M-fouyir', 'J-fouyir', 'M.Houney', 'M.Nollick'], + 'weekdays' => ['Jedoonee', 'Jelhein', 'Jemayrt', 'Jercean', 'Jerdein', 'Jeheiney', 'Jesarn'], + 'weekdays_short' => ['Jed', 'Jel', 'Jem', 'Jerc', 'Jerd', 'Jeh', 'Jes'], + 'weekdays_min' => ['Jed', 'Jel', 'Jem', 'Jerc', 'Jerd', 'Jeh', 'Jes'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count blein', + 'y' => ':count blein', + 'a_year' => ':count blein', + + 'month' => ':count mee', + 'm' => ':count mee', + 'a_month' => ':count mee', + + 'week' => ':count shiaghtin', + 'w' => ':count shiaghtin', + 'a_week' => ':count shiaghtin', + + 'day' => ':count laa', + 'd' => ':count laa', + 'a_day' => ':count laa', + + 'hour' => ':count oor', + 'h' => ':count oor', + 'a_hour' => ':count oor', + + 'minute' => ':count feer veg', + 'min' => ':count feer veg', + 'a_minute' => ':count feer veg', + + 'second' => ':count derrey', + 's' => ':count derrey', + 'a_second' => ':count derrey', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ha.php b/libraries/Carbon/src/Carbon/Lang/ha.php new file mode 100644 index 00000000000..03682873f17 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ha.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM, YYYY HH:mm', + ], + 'months' => ['Janairu', 'Faburairu', 'Maris', 'Afirilu', 'Mayu', 'Yuni', 'Yuli', 'Agusta', 'Satumba', 'Oktoba', 'Nuwamba', 'Disamba'], + 'months_short' => ['Jan', 'Fab', 'Mar', 'Afi', 'May', 'Yun', 'Yul', 'Agu', 'Sat', 'Okt', 'Nuw', 'Dis'], + 'weekdays' => ['Lahadi', 'Litini', 'Talata', 'Laraba', 'Alhamis', 'Jumaʼa', 'Asabar'], + 'weekdays_short' => ['Lah', 'Lit', 'Tal', 'Lar', 'Alh', 'Jum', 'Asa'], + 'weekdays_min' => ['Lh', 'Li', 'Ta', 'Lr', 'Al', 'Ju', 'As'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'shekara :count', + 'y' => 'shekara :count', + 'a_year' => 'shekara :count', + + 'month' => ':count wátàa', + 'm' => ':count wátàa', + 'a_month' => ':count wátàa', + + 'week' => ':count mako', + 'w' => ':count mako', + 'a_week' => ':count mako', + + 'day' => ':count rana', + 'd' => ':count rana', + 'a_day' => ':count rana', + + 'hour' => ':count áwàa', + 'h' => ':count áwàa', + 'a_hour' => ':count áwàa', + + 'minute' => 'minti :count', + 'min' => 'minti :count', + 'a_minute' => 'minti :count', + + 'second' => ':count ná bíyú', + 's' => ':count ná bíyú', + 'a_second' => ':count ná bíyú', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ha_GH.php b/libraries/Carbon/src/Carbon/Lang/ha_GH.php new file mode 100644 index 00000000000..c8fd1820409 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ha_GH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ha_NE.php b/libraries/Carbon/src/Carbon/Lang/ha_NE.php new file mode 100644 index 00000000000..c8fd1820409 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ha_NE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ha_NG.php b/libraries/Carbon/src/Carbon/Lang/ha_NG.php new file mode 100644 index 00000000000..c8fd1820409 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ha_NG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hak.php b/libraries/Carbon/src/Carbon/Lang/hak.php new file mode 100644 index 00000000000..c20f84c1d11 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hak.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hak_TW.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hak_TW.php b/libraries/Carbon/src/Carbon/Lang/hak_TW.php new file mode 100644 index 00000000000..f1ec9de418b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hak_TW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['禮拜日', '禮拜一', '禮拜二', '禮拜三', '禮拜四', '禮拜五', '禮拜六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['上晝', '下晝'], + + 'year' => ':count ngien11', + 'y' => ':count ngien11', + 'a_year' => ':count ngien11', + + 'month' => ':count ngie̍t', + 'm' => ':count ngie̍t', + 'a_month' => ':count ngie̍t', + + 'week' => ':count lî-pai', + 'w' => ':count lî-pai', + 'a_week' => ':count lî-pai', + + 'day' => ':count ngit', + 'd' => ':count ngit', + 'a_day' => ':count ngit', + + 'hour' => ':count sṳ̀', + 'h' => ':count sṳ̀', + 'a_hour' => ':count sṳ̀', + + 'minute' => ':count fûn', + 'min' => ':count fûn', + 'a_minute' => ':count fûn', + + 'second' => ':count miéu', + 's' => ':count miéu', + 'a_second' => ':count miéu', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/haw.php b/libraries/Carbon/src/Carbon/Lang/haw.php new file mode 100644 index 00000000000..0c5092c05e0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/haw.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['Ianuali', 'Pepeluali', 'Malaki', 'ʻApelila', 'Mei', 'Iune', 'Iulai', 'ʻAukake', 'Kepakemapa', 'ʻOkakopa', 'Nowemapa', 'Kekemapa'], + 'months_short' => ['Ian.', 'Pep.', 'Mal.', 'ʻAp.', 'Mei', 'Iun.', 'Iul.', 'ʻAu.', 'Kep.', 'ʻOk.', 'Now.', 'Kek.'], + 'weekdays' => ['Lāpule', 'Poʻakahi', 'Poʻalua', 'Poʻakolu', 'Poʻahā', 'Poʻalima', 'Poʻaono'], + 'weekdays_short' => ['LP', 'P1', 'P2', 'P3', 'P4', 'P5', 'P6'], + 'weekdays_min' => ['S', 'M', 'T', 'W', 'T', 'F', 'S'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count makahiki', + 'y' => ':count makahiki', + 'a_year' => ':count makahiki', + + 'month' => ':count mahina', + 'm' => ':count mahina', + 'a_month' => ':count mahina', + + 'week' => ':count pule', + 'w' => ':count pule', + 'a_week' => ':count pule', + + 'day' => ':count lā', + 'd' => ':count lā', + 'a_day' => ':count lā', + + 'hour' => ':count hola', + 'h' => ':count hola', + 'a_hour' => ':count hola', + + 'minute' => ':count minuke', + 'min' => ':count minuke', + 'a_minute' => ':count minuke', + + 'second' => ':count lua', + 's' => ':count lua', + 'a_second' => ':count lua', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/he.php b/libraries/Carbon/src/Carbon/Lang/he.php new file mode 100644 index 00000000000..395187faf86 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/he.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Daniel Cohen Gindi + * - JD Isaacks + * - Itai Nathaniel + * - GabMic + * - Yaakov Dahan (yakidahan) + */ +return [ + 'year' => 'שנה|{2}שנתיים|:count שנים', + 'y' => 'שנה|:count שנ׳', + 'month' => 'חודש|{2}חודשיים|:count חודשים', + 'm' => 'חודש|:count חו׳', + 'week' => 'שבוע|{2}שבועיים|:count שבועות', + 'w' => 'שבוע|:count שב׳', + 'day' => 'יום|{2}יומיים|:count ימים', + 'd' => 'יום|:count ימ׳', + 'hour' => 'שעה|{2}שעתיים|:count שעות', + 'h' => 'שעה|:count שע׳', + 'minute' => 'דקה|{2}שתי דקות|:count דקות', + 'min' => 'דקה|:count דק׳', + 'second' => 'שנייה|:count שניות', + 'a_second' => 'כמה שניות|:count שניות', + 's' => 'שניה|:count שנ׳', + 'ago' => 'לפני :time', + 'from_now' => 'בעוד :time מעכשיו', + 'after' => 'אחרי :time', + 'before' => 'לפני :time', + 'diff_now' => 'עכשיו', + 'diff_today' => 'היום', + 'diff_today_regexp' => 'היום(?:\\s+ב־)?', + 'diff_yesterday' => 'אתמול', + 'diff_yesterday_regexp' => 'אתמול(?:\\s+ב־)?', + 'diff_tomorrow' => 'מחר', + 'diff_tomorrow_regexp' => 'מחר(?:\\s+ב־)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [ב]MMMM YYYY', + 'LLL' => 'D [ב]MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D [ב]MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[היום ב־]LT', + 'nextDay' => '[מחר ב־]LT', + 'nextWeek' => 'dddd [בשעה] LT', + 'lastDay' => '[אתמול ב־]LT', + 'lastWeek' => '[ביום] dddd [האחרון בשעה] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour, $minute, $isLower) { + if ($hour < 5) { + return 'לפנות בוקר'; + } + if ($hour < 10) { + return 'בבוקר'; + } + if ($hour < 12) { + return $isLower ? 'לפנה"צ' : 'לפני הצהריים'; + } + if ($hour < 18) { + return $isLower ? 'אחה"צ' : 'אחרי הצהריים'; + } + + return 'בערב'; + }, + 'months' => ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], + 'months_short' => ['ינו׳', 'פבר׳', 'מרץ', 'אפר׳', 'מאי', 'יוני', 'יולי', 'אוג׳', 'ספט׳', 'אוק׳', 'נוב׳', 'דצמ׳'], + 'weekdays' => ['ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת'], + 'weekdays_short' => ['א׳', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳'], + 'weekdays_min' => ['א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ש'], + 'list' => [', ', ' ו -'], + 'weekend' => [5, 6], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/he_IL.php b/libraries/Carbon/src/Carbon/Lang/he_IL.php new file mode 100644 index 00000000000..11936b5f9c8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/he_IL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/he.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hi.php b/libraries/Carbon/src/Carbon/Lang/hi.php new file mode 100644 index 00000000000..a4e6028e7fb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hi.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - abhimanyu003 + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => 'एक वर्ष|:count वर्ष', + 'y' => '1 वर्ष|:count वर्षों', + 'month' => 'एक महीने|:count महीने', + 'm' => '1 माह|:count महीने', + 'week' => '1 सप्ताह|:count सप्ताह', + 'w' => '1 सप्ताह|:count सप्ताह', + 'day' => 'एक दिन|:count दिन', + 'd' => '1 दिन|:count दिनों', + 'hour' => 'एक घंटा|:count घंटे', + 'h' => '1 घंटा|:count घंटे', + 'minute' => 'एक मिनट|:count मिनट', + 'min' => '1 मिनट|:count मिनटों', + 'second' => 'कुछ ही क्षण|:count सेकंड', + 's' => '1 सेकंड|:count सेकंड', + 'ago' => ':time पहले', + 'from_now' => ':time में', + 'after' => ':time के बाद', + 'before' => ':time के पहले', + 'diff_now' => 'अब', + 'diff_today' => 'आज', + 'diff_yesterday' => 'कल', + 'diff_tomorrow' => 'कल', + 'formats' => [ + 'LT' => 'A h:mm बजे', + 'LTS' => 'A h:mm:ss बजे', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm बजे', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm बजे', + ], + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[कल] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[कल] LT', + 'lastWeek' => '[पिछले] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'रात'; + } + if ($hour < 10) { + return 'सुबह'; + } + if ($hour < 17) { + return 'दोपहर'; + } + if ($hour < 20) { + return 'शाम'; + } + + return 'रात'; + }, + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जन.', 'फ़र.', 'मार्च', 'अप्रै.', 'मई', 'जून', 'जुल.', 'अग.', 'सित.', 'अक्टू.', 'नव.', 'दिस.'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरूवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरू', 'शुक्र', 'शनि'], + 'weekdays_min' => ['र', 'सो', 'मं', 'बु', 'गु', 'शु', 'श'], + 'list' => [', ', ' और '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/hi_IN.php b/libraries/Carbon/src/Carbon/Lang/hi_IN.php new file mode 100644 index 00000000000..dbf1231904d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hi_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hi.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hif.php b/libraries/Carbon/src/Carbon/Lang/hif.php new file mode 100644 index 00000000000..996f3b047a8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hif.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hif_FJ.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hif_FJ.php b/libraries/Carbon/src/Carbon/Lang/hif_FJ.php new file mode 100644 index 00000000000..4caf52f2552 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hif_FJ.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Ravivar', 'Somvar', 'Mangalvar', 'Budhvar', 'Guruvar', 'Shukravar', 'Shanivar'], + 'weekdays_short' => ['Ravi', 'Som', 'Mangal', 'Budh', 'Guru', 'Shukra', 'Shani'], + 'weekdays_min' => ['Ravi', 'Som', 'Mangal', 'Budh', 'Guru', 'Shukra', 'Shani'], + 'meridiem' => ['Purvahan', 'Aparaahna'], + + 'hour' => ':count minit', // less reliable + 'h' => ':count minit', // less reliable + 'a_hour' => ':count minit', // less reliable + + 'year' => ':count saal', + 'y' => ':count saal', + 'a_year' => ':count saal', + + 'month' => ':count Mahina', + 'm' => ':count Mahina', + 'a_month' => ':count Mahina', + + 'week' => ':count Hafta', + 'w' => ':count Hafta', + 'a_week' => ':count Hafta', + + 'day' => ':count Din', + 'd' => ':count Din', + 'a_day' => ':count Din', + + 'minute' => ':count Minit', + 'min' => ':count Minit', + 'a_minute' => ':count Minit', + + 'second' => ':count Second', + 's' => ':count Second', + 'a_second' => ':count Second', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/hne.php b/libraries/Carbon/src/Carbon/Lang/hne.php new file mode 100644 index 00000000000..4bced34f4a8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hne.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hne_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hne_IN.php b/libraries/Carbon/src/Carbon/Lang/hne_IN.php new file mode 100644 index 00000000000..450d440d899 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hne_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अपरेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितमबर', 'अकटूबर', 'नवमबर', 'दिसमबर'], + 'months_short' => ['जन', 'फर', 'मार्च', 'अप', 'मई', 'जून', 'जुला', 'अग', 'सित', 'अकटू', 'नव', 'दिस'], + 'weekdays' => ['इतवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'बिरसपत', 'सुकरवार', 'सनिवार'], + 'weekdays_short' => ['इत', 'सोम', 'मंग', 'बुध', 'बिर', 'सुक', 'सनि'], + 'weekdays_min' => ['इत', 'सोम', 'मंग', 'बुध', 'बिर', 'सुक', 'सनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['बिहिनियाँ', 'मंझनियाँ'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/hr.php b/libraries/Carbon/src/Carbon/Lang/hr.php new file mode 100644 index 00000000000..d228c1d779e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hr.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Tim Fish + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - tomhorvat + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - tomhorvat + * - Stjepan Majdak + * - Vanja Retkovac (vr00) + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count godinu|:count godine|:count godina', + 'y' => ':count god.|:count god.|:count god.', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mj.|:count mj.|:count mj.', + 'week' => ':count tjedan|:count tjedna|:count tjedana', + 'w' => ':count tj.|:count tj.|:count tj.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.|:count d.|:count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minutu|:count minute|:count minuta', + 'min' => ':count min.|:count min.|:count min.', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 'a_second' => 'nekoliko sekundi|:count sekunde|:count sekundi', + 's' => ':count sek.|:count sek.|:count sek.', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time poslije', + 'before' => ':time prije', + 'diff_now' => 'sad', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'jučer', + 'diff_yesterday_regexp' => 'jučer(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'diff_before_yesterday' => 'prekjučer', + 'diff_after_tomorrow' => 'prekosutra', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D. M. YYYY.', + 'LL' => 'D. MMMM YYYY.', + 'LLL' => 'D. MMMM YYYY. H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY. H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[jučer u] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + default: + return '[prošli] dddd [u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['siječnja', 'veljače', 'ožujka', 'travnja', 'svibnja', 'lipnja', 'srpnja', 'kolovoza', 'rujna', 'listopada', 'studenoga', 'prosinca'], + 'months_standalone' => ['siječanj', 'veljača', 'ožujak', 'travanj', 'svibanj', 'lipanj', 'srpanj', 'kolovoz', 'rujan', 'listopad', 'studeni', 'prosinac'], + 'months_short' => ['sij.', 'velj.', 'ožu.', 'tra.', 'svi.', 'lip.', 'srp.', 'kol.', 'ruj.', 'lis.', 'stu.', 'pro.'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['nedjelju', 'ponedjeljak', 'utorak', 'srijedu', 'četvrtak', 'petak', 'subotu'], + 'weekdays_standalone' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/hr_BA.php b/libraries/Carbon/src/Carbon/Lang/hr_BA.php new file mode 100644 index 00000000000..89c0c15579d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hr_BA.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - DarkoDevelop + */ +return array_replace_recursive(require __DIR__.'/hr.php', [ + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned', 'pon', 'uto', 'sri', 'čet', 'pet', 'sub'], + 'weekdays_min' => ['ned', 'pon', 'uto', 'sri', 'čet', 'pet', 'sub'], + 'months' => ['siječnja', 'veljače', 'ožujka', 'travnja', 'svibnja', 'lipnja', 'srpnja', 'kolovoza', 'rujna', 'listopada', 'studenoga', 'prosinca'], + 'months_short' => ['sij', 'velj', 'ožu', 'tra', 'svi', 'lip', 'srp', 'kol', 'ruj', 'lis', 'stu', 'pro'], + 'months_standalone' => ['siječanj', 'veljača', 'ožujak', 'travanj', 'svibanj', 'lipanj', 'srpanj', 'kolovoz', 'rujan', 'listopad', 'studeni', 'prosinac'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D. M. yy.', + 'LL' => 'D. MMM YYYY.', + 'LLL' => 'D. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY. HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/hr_HR.php b/libraries/Carbon/src/Carbon/Lang/hr_HR.php new file mode 100644 index 00000000000..599b67de7f0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hr_HR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hsb.php b/libraries/Carbon/src/Carbon/Lang/hsb.php new file mode 100644 index 00000000000..6852dfa1eee --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hsb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hsb_DE.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hsb_DE.php b/libraries/Carbon/src/Carbon/Lang/hsb_DE.php new file mode 100644 index 00000000000..5333b1b9234 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hsb_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from Michael Wolf Andrzej Krzysztofowicz ankry@mif.pg.gda.pl + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'DD. MMMM, HH:mm [hodź.]', + 'LLLL' => 'dddd, DD. MMMM YYYY, HH:mm [hodź.]', + ], + 'months' => ['januara', 'februara', 'měrca', 'apryla', 'meje', 'junija', 'julija', 'awgusta', 'septembra', 'oktobra', 'nowembra', 'decembra'], + 'months_short' => ['Jan', 'Feb', 'Měr', 'Apr', 'Mej', 'Jun', 'Jul', 'Awg', 'Sep', 'Okt', 'Now', 'Dec'], + 'weekdays' => ['Njedźela', 'Póndźela', 'Wutora', 'Srjeda', 'Štvórtk', 'Pjatk', 'Sobota'], + 'weekdays_short' => ['Nj', 'Pó', 'Wu', 'Sr', 'Št', 'Pj', 'So'], + 'weekdays_min' => ['Nj', 'Pó', 'Wu', 'Sr', 'Št', 'Pj', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count lěto', + 'y' => ':count lěto', + 'a_year' => ':count lěto', + + 'month' => ':count měsac', + 'm' => ':count měsac', + 'a_month' => ':count měsac', + + 'week' => ':count tydźeń', + 'w' => ':count tydźeń', + 'a_week' => ':count tydźeń', + + 'day' => ':count dźeń', + 'd' => ':count dźeń', + 'a_day' => ':count dźeń', + + 'hour' => ':count hodźina', + 'h' => ':count hodźina', + 'a_hour' => ':count hodźina', + + 'minute' => ':count chwila', + 'min' => ':count chwila', + 'a_minute' => ':count chwila', + + 'second' => ':count druhi', + 's' => ':count druhi', + 'a_second' => ':count druhi', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ht.php b/libraries/Carbon/src/Carbon/Lang/ht.php new file mode 100644 index 00000000000..9132c635b95 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ht.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ht_HT.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ht_HT.php b/libraries/Carbon/src/Carbon/Lang/ht_HT.php new file mode 100644 index 00000000000..01d282b8986 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ht_HT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['janvye', 'fevriye', 'mas', 'avril', 'me', 'jen', 'jiyè', 'out', 'septanm', 'oktòb', 'novanm', 'desanm'], + 'months_short' => ['jan', 'fev', 'mas', 'avr', 'me', 'jen', 'jiy', 'out', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['dimanch', 'lendi', 'madi', 'mèkredi', 'jedi', 'vandredi', 'samdi'], + 'weekdays_short' => ['dim', 'len', 'mad', 'mèk', 'jed', 'van', 'sam'], + 'weekdays_min' => ['dim', 'len', 'mad', 'mèk', 'jed', 'van', 'sam'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count lane', + 'y' => ':count lane', + 'a_year' => ':count lane', + + 'month' => 'mwa :count', + 'm' => 'mwa :count', + 'a_month' => 'mwa :count', + + 'week' => 'semèn :count', + 'w' => 'semèn :count', + 'a_week' => 'semèn :count', + + 'day' => ':count jou', + 'd' => ':count jou', + 'a_day' => ':count jou', + + 'hour' => ':count lè', + 'h' => ':count lè', + 'a_hour' => ':count lè', + + 'minute' => ':count minit', + 'min' => ':count minit', + 'a_minute' => ':count minit', + + 'second' => ':count segonn', + 's' => ':count segonn', + 'a_second' => ':count segonn', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/hu.php b/libraries/Carbon/src/Carbon/Lang/hu.php new file mode 100644 index 00000000000..9bfc4dc8d4a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hu.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Adam Brunner + * - Brett Johnson + * - balping + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +$huWeekEndings = ['vasárnap', 'hétfőn', 'kedden', 'szerdán', 'csütörtökön', 'pénteken', 'szombaton']; + +return [ + 'year' => ':count év', + 'y' => ':count év', + 'month' => ':count hónap', + 'm' => ':count hónap', + 'week' => ':count hét', + 'w' => ':count hét', + 'day' => ':count nap', + 'd' => ':count nap', + 'hour' => ':count óra', + 'h' => ':count óra', + 'minute' => ':count perc', + 'min' => ':count perc', + 'second' => ':count másodperc', + 's' => ':count másodperc', + 'ago' => ':time', + 'from_now' => ':time múlva', + 'after' => ':time később', + 'before' => ':time korábban', + 'year_ago' => ':count éve', + 'y_ago' => ':count éve', + 'month_ago' => ':count hónapja', + 'm_ago' => ':count hónapja', + 'week_ago' => ':count hete', + 'w_ago' => ':count hete', + 'day_ago' => ':count napja', + 'd_ago' => ':count napja', + 'hour_ago' => ':count órája', + 'h_ago' => ':count órája', + 'minute_ago' => ':count perce', + 'min_ago' => ':count perce', + 'second_ago' => ':count másodperce', + 's_ago' => ':count másodperce', + 'year_after' => ':count évvel', + 'y_after' => ':count évvel', + 'month_after' => ':count hónappal', + 'm_after' => ':count hónappal', + 'week_after' => ':count héttel', + 'w_after' => ':count héttel', + 'day_after' => ':count nappal', + 'd_after' => ':count nappal', + 'hour_after' => ':count órával', + 'h_after' => ':count órával', + 'minute_after' => ':count perccel', + 'min_after' => ':count perccel', + 'second_after' => ':count másodperccel', + 's_after' => ':count másodperccel', + 'year_before' => ':count évvel', + 'y_before' => ':count évvel', + 'month_before' => ':count hónappal', + 'm_before' => ':count hónappal', + 'week_before' => ':count héttel', + 'w_before' => ':count héttel', + 'day_before' => ':count nappal', + 'd_before' => ':count nappal', + 'hour_before' => ':count órával', + 'h_before' => ':count órával', + 'minute_before' => ':count perccel', + 'min_before' => ':count perccel', + 'second_before' => ':count másodperccel', + 's_before' => ':count másodperccel', + 'months' => ['január', 'február', 'március', 'április', 'május', 'június', 'július', 'augusztus', 'szeptember', 'október', 'november', 'december'], + 'months_short' => ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat'], + 'weekdays_short' => ['vas', 'hét', 'kedd', 'sze', 'csüt', 'pén', 'szo'], + 'weekdays_min' => ['v', 'h', 'k', 'sze', 'cs', 'p', 'sz'], + 'ordinal' => ':number.', + 'diff_now' => 'most', + 'diff_today' => 'ma', + 'diff_yesterday' => 'tegnap', + 'diff_tomorrow' => 'holnap', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'YYYY.MM.DD.', + 'LL' => 'YYYY. MMMM D.', + 'LLL' => 'YYYY. MMMM D. H:mm', + 'LLLL' => 'YYYY. MMMM D., dddd H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ma] LT[-kor]', + 'nextDay' => '[holnap] LT[-kor]', + 'nextWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + return '['.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; + }, + 'lastDay' => '[tegnap] LT[-kor]', + 'lastWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + return '[múlt '.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; + }, + 'sameElse' => 'L', + ], + 'meridiem' => ['DE', 'DU'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' és '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/hu_HU.php b/libraries/Carbon/src/Carbon/Lang/hu_HU.php new file mode 100644 index 00000000000..ed8b885673f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hu_HU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hu.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/hy.php b/libraries/Carbon/src/Carbon/Lang/hy.php new file mode 100644 index 00000000000..a15593f65e4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hy.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - mhamlet + */ +return [ + 'year' => ':count տարի', + 'a_year' => 'տարի|:count տարի', + 'y' => ':countտ', + 'month' => ':count ամիս', + 'a_month' => 'ամիս|:count ամիս', + 'm' => ':countամ', + 'week' => ':count շաբաթ', + 'a_week' => 'շաբաթ|:count շաբաթ', + 'w' => ':countշ', + 'day' => ':count օր', + 'a_day' => 'օր|:count օր', + 'd' => ':countօր', + 'hour' => ':count ժամ', + 'a_hour' => 'ժամ|:count ժամ', + 'h' => ':countժ', + 'minute' => ':count րոպե', + 'a_minute' => 'րոպե|:count րոպե', + 'min' => ':countր', + 'second' => ':count վայրկյան', + 'a_second' => 'մի քանի վայրկյան|:count վայրկյան', + 's' => ':countվրկ', + 'ago' => ':time առաջ', + 'from_now' => ':timeից', + 'after' => ':time հետո', + 'before' => ':time առաջ', + 'diff_now' => 'հիմա', + 'diff_today' => 'այսօր', + 'diff_yesterday' => 'երեկ', + 'diff_tomorrow' => 'վաղը', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY թ.', + 'LLL' => 'D MMMM YYYY թ., HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY թ., HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[այսօր] LT', + 'nextDay' => '[վաղը] LT', + 'nextWeek' => 'dddd [օրը ժամը] LT', + 'lastDay' => '[երեկ] LT', + 'lastWeek' => '[անցած] dddd [օրը ժամը] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'DDD': + case 'w': + case 'W': + case 'DDDo': + return $number.($number === 1 ? '-ին' : '-րդ'); + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'գիշերվա'; + } + if ($hour < 12) { + return 'առավոտվա'; + } + if ($hour < 17) { + return 'ցերեկվա'; + } + + return 'երեկոյան'; + }, + 'months' => ['հունվարի', 'փետրվարի', 'մարտի', 'ապրիլի', 'մայիսի', 'հունիսի', 'հուլիսի', 'օգոստոսի', 'սեպտեմբերի', 'հոկտեմբերի', 'նոյեմբերի', 'դեկտեմբերի'], + 'months_standalone' => ['հունվար', 'փետրվար', 'մարտ', 'ապրիլ', 'մայիս', 'հունիս', 'հուլիս', 'օգոստոս', 'սեպտեմբեր', 'հոկտեմբեր', 'նոյեմբեր', 'դեկտեմբեր'], + 'months_short' => ['հնվ', 'փտր', 'մրտ', 'ապր', 'մյս', 'հնս', 'հլս', 'օգս', 'սպտ', 'հկտ', 'նմբ', 'դկտ'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['կիրակի', 'երկուշաբթի', 'երեքշաբթի', 'չորեքշաբթի', 'հինգշաբթի', 'ուրբաթ', 'շաբաթ'], + 'weekdays_short' => ['կրկ', 'երկ', 'երք', 'չրք', 'հնգ', 'ուրբ', 'շբթ'], + 'weekdays_min' => ['կրկ', 'երկ', 'երք', 'չրք', 'հնգ', 'ուրբ', 'շբթ'], + 'list' => [', ', ' եւ '], + 'first_day_of_week' => 1, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/hy_AM.php b/libraries/Carbon/src/Carbon/Lang/hy_AM.php new file mode 100644 index 00000000000..f406343f4c6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/hy_AM.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Tim Fish + * - Serhan Apaydın + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/hy.php', [ + 'from_now' => ':time հետո', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/i18n.php b/libraries/Carbon/src/Carbon/Lang/i18n.php new file mode 100644 index 00000000000..cffffe8bd10 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/i18n.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], + 'months_short' => ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], + 'weekdays' => ['1', '2', '3', '4', '5', '6', '7'], + 'weekdays_short' => ['1', '2', '3', '4', '5', '6', '7'], + 'weekdays_min' => ['1', '2', '3', '4', '5', '6', '7'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ia.php b/libraries/Carbon/src/Carbon/Lang/ia.php new file mode 100644 index 00000000000..606e215aaed --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ia.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ia_FR.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ia_FR.php b/libraries/Carbon/src/Carbon/Lang/ia_FR.php new file mode 100644 index 00000000000..b1d61a5de7b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ia_FR.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Fedora Project Nik Kalach nikka@fedoraproject.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['januario', 'februario', 'martio', 'april', 'maio', 'junio', 'julio', 'augusto', 'septembre', 'octobre', 'novembre', 'decembre'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], + 'weekdays' => ['dominica', 'lunedi', 'martedi', 'mercuridi', 'jovedi', 'venerdi', 'sabbato'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mer', 'jov', 'ven', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mer', 'jov', 'ven', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => 'anno :count', + 'y' => 'anno :count', + 'a_year' => 'anno :count', + + 'month' => ':count mense', + 'm' => ':count mense', + 'a_month' => ':count mense', + + 'week' => ':count septimana', + 'w' => ':count septimana', + 'a_week' => ':count septimana', + + 'day' => ':count die', + 'd' => ':count die', + 'a_day' => ':count die', + + 'hour' => ':count hora', + 'h' => ':count hora', + 'a_hour' => ':count hora', + + 'minute' => ':count minuscule', + 'min' => ':count minuscule', + 'a_minute' => ':count minuscule', + + 'second' => ':count secunda', + 's' => ':count secunda', + 'a_second' => ':count secunda', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/id.php b/libraries/Carbon/src/Carbon/Lang/id.php new file mode 100644 index 00000000000..4f9b637ff1e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/id.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - du + * - JD Isaacks + * - Nafies Luthfi + * - Raymundus Jati Primanda (mundusjp) + * - diankur313 + * - a-wip0 + */ +return [ + 'year' => ':count tahun', + 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'y' => ':countthn', + 'month' => ':count bulan', + 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'm' => ':countbln', + 'week' => ':count minggu', + 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'w' => ':countmgg', + 'day' => ':count hari', + 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'd' => ':counthr', + 'hour' => ':count jam', + 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'h' => ':countj', + 'minute' => ':count menit', + 'a_minute' => '{1}semenit|]1,Inf[:count menit', + 'min' => ':countmnt', + 'second' => ':count detik', + 'a_second' => '{1}beberapa detik|]1,Inf[:count detik', + 's' => ':countdt', + 'ago' => ':time yang lalu', + 'from_now' => ':time dari sekarang', + 'after' => ':time setelahnya', + 'before' => ':time sebelumnya', + 'diff_now' => 'sekarang', + 'diff_today' => 'Hari', + 'diff_today_regexp' => 'Hari(?:\\s+ini)?(?:\\s+pukul)?', + 'diff_yesterday' => 'kemarin', + 'diff_yesterday_regexp' => 'Kemarin(?:\\s+pukul)?', + 'diff_tomorrow' => 'besok', + 'diff_tomorrow_regexp' => 'Besok(?:\\s+pukul)?', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Hari ini pukul] LT', + 'nextDay' => '[Besok pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kemarin pukul] LT', + 'lastWeek' => 'dddd [lalu pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'pagi'; + } + if ($hour < 15) { + return 'siang'; + } + if ($hour < 19) { + return 'sore'; + } + + return 'malam'; + }, + 'months' => ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agt', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'], + 'weekdays_short' => ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'], + 'weekdays_min' => ['Mg', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' dan '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/id_ID.php b/libraries/Carbon/src/Carbon/Lang/id_ID.php new file mode 100644 index 00000000000..10ad46326e5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/id_ID.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/id.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ig.php b/libraries/Carbon/src/Carbon/Lang/ig.php new file mode 100644 index 00000000000..5da6d8d2100 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ig.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ig_NG.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ig_NG.php b/libraries/Carbon/src/Carbon/Lang/ig_NG.php new file mode 100644 index 00000000000..f894d98d016 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ig_NG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Jenụwarị', 'Febrụwarị', 'Maachị', 'Eprel', 'Mee', 'Juun', 'Julaị', 'Ọgọọst', 'Septemba', 'Ọktoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jen', 'Feb', 'Maa', 'Epr', 'Mee', 'Juu', 'Jul', 'Ọgọ', 'Sep', 'Ọkt', 'Nov', 'Dis'], + 'weekdays' => ['sọnde', 'mọnde', 'tuzde', 'wenzde', 'tọsde', 'fraịde', 'satọde'], + 'weekdays_short' => ['sọn', 'mọn', 'tuz', 'wen', 'tọs', 'fra', 'sat'], + 'weekdays_min' => ['sọn', 'mọn', 'tuz', 'wen', 'tọs', 'fra', 'sat'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'afo :count', + 'y' => 'afo :count', + 'a_year' => 'afo :count', + + 'month' => 'önwa :count', + 'm' => 'önwa :count', + 'a_month' => 'önwa :count', + + 'week' => 'izu :count', + 'w' => 'izu :count', + 'a_week' => 'izu :count', + + 'day' => 'ụbọchị :count', + 'd' => 'ụbọchị :count', + 'a_day' => 'ụbọchị :count', + + 'hour' => 'awa :count', + 'h' => 'awa :count', + 'a_hour' => 'awa :count', + + 'minute' => 'minit :count', + 'min' => 'minit :count', + 'a_minute' => 'minit :count', + + 'second' => 'sekọnd :count', + 's' => 'sekọnd :count', + 'a_second' => 'sekọnd :count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ii.php b/libraries/Carbon/src/Carbon/Lang/ii.php new file mode 100644 index 00000000000..fb49d237586 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ii.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ꎸꄑ', 'ꁯꋒ'], + 'weekdays' => ['ꑭꆏꑍ', 'ꆏꊂꋍ', 'ꆏꊂꑍ', 'ꆏꊂꌕ', 'ꆏꊂꇖ', 'ꆏꊂꉬ', 'ꆏꊂꃘ'], + 'weekdays_short' => ['ꑭꆏ', 'ꆏꋍ', 'ꆏꑍ', 'ꆏꌕ', 'ꆏꇖ', 'ꆏꉬ', 'ꆏꃘ'], + 'weekdays_min' => ['ꑭꆏ', 'ꆏꋍ', 'ꆏꑍ', 'ꆏꌕ', 'ꆏꇖ', 'ꆏꉬ', 'ꆏꃘ'], + 'months' => null, + 'months_short' => ['ꋍꆪ', 'ꑍꆪ', 'ꌕꆪ', 'ꇖꆪ', 'ꉬꆪ', 'ꃘꆪ', 'ꏃꆪ', 'ꉆꆪ', 'ꈬꆪ', 'ꊰꆪ', 'ꊰꊪꆪ', 'ꊰꑋꆪ'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D h:mm a', + 'LLLL' => 'YYYY MMMM D, dddd h:mm a', + ], + + 'year' => ':count ꒉ', // less reliable + 'y' => ':count ꒉ', // less reliable + 'a_year' => ':count ꒉ', // less reliable + + 'month' => ':count ꆪ', + 'm' => ':count ꆪ', + 'a_month' => ':count ꆪ', + + 'week' => ':count ꏃ', // less reliable + 'w' => ':count ꏃ', // less reliable + 'a_week' => ':count ꏃ', // less reliable + + 'day' => ':count ꏜ', // less reliable + 'd' => ':count ꏜ', // less reliable + 'a_day' => ':count ꏜ', // less reliable + + 'hour' => ':count ꄮꈉ', + 'h' => ':count ꄮꈉ', + 'a_hour' => ':count ꄮꈉ', + + 'minute' => ':count ꀄꊭ', // less reliable + 'min' => ':count ꀄꊭ', // less reliable + 'a_minute' => ':count ꀄꊭ', // less reliable + + 'second' => ':count ꇅ', // less reliable + 's' => ':count ꇅ', // less reliable + 'a_second' => ':count ꇅ', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ik.php b/libraries/Carbon/src/Carbon/Lang/ik.php new file mode 100644 index 00000000000..b295970d240 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ik.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ik_CA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ik_CA.php b/libraries/Carbon/src/Carbon/Lang/ik_CA.php new file mode 100644 index 00000000000..5f4b2c44b16 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ik_CA.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Siqiññaatchiaq', 'Siqiññaasrugruk', 'Paniqsiqsiivik', 'Qilġich Tatqiat', 'Suppivik', 'Iġñivik', 'Itchavik', 'Tiññivik', 'Amiġaiqsivik', 'Sikkuvik', 'Nippivik', 'Siqiñġiḷaq'], + 'months_short' => ['Sñt', 'Sñs', 'Pan', 'Qil', 'Sup', 'Iġñ', 'Itc', 'Tiñ', 'Ami', 'Sik', 'Nip', 'Siq'], + 'weekdays' => ['Minġuiqsioiq', 'Savałłiq', 'Ilaqtchiioiq', 'Qitchiioiq', 'Sisamiioiq', 'Tallimmiioiq', 'Maqinġuoiq'], + 'weekdays_short' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'weekdays_min' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ukiuq', + 'y' => ':count ukiuq', + 'a_year' => ':count ukiuq', + + 'month' => ':count Tatqiat', + 'm' => ':count Tatqiat', + 'a_month' => ':count Tatqiat', + + 'week' => ':count tatqiat', // less reliable + 'w' => ':count tatqiat', // less reliable + 'a_week' => ':count tatqiat', // less reliable + + 'day' => ':count siqiñiq', // less reliable + 'd' => ':count siqiñiq', // less reliable + 'a_day' => ':count siqiñiq', // less reliable + + 'hour' => ':count Siḷa', // less reliable + 'h' => ':count Siḷa', // less reliable + 'a_hour' => ':count Siḷa', // less reliable + + 'second' => ':count iġñiq', // less reliable + 's' => ':count iġñiq', // less reliable + 'a_second' => ':count iġñiq', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/in.php b/libraries/Carbon/src/Carbon/Lang/in.php new file mode 100644 index 00000000000..10ad46326e5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/in.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/id.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/is.php b/libraries/Carbon/src/Carbon/Lang/is.php new file mode 100644 index 00000000000..15f2c8b7890 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/is.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kristján Ingi Geirsson + */ +return [ + 'year' => '1 ár|:count ár', + 'y' => '1 ár|:count ár', + 'month' => '1 mánuður|:count mánuðir', + 'm' => '1 mánuður|:count mánuðir', + 'week' => '1 vika|:count vikur', + 'w' => '1 vika|:count vikur', + 'day' => '1 dagur|:count dagar', + 'd' => '1 dagur|:count dagar', + 'hour' => '1 klukkutími|:count klukkutímar', + 'h' => '1 klukkutími|:count klukkutímar', + 'minute' => '1 mínúta|:count mínútur', + 'min' => '1 mínúta|:count mínútur', + 'second' => '1 sekúnda|:count sekúndur', + 's' => '1 sekúnda|:count sekúndur', + 'ago' => ':time síðan', + 'from_now' => ':time síðan', + 'after' => ':time eftir', + 'before' => ':time fyrir', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], + 'meridiem' => ['fh', 'eh'], + 'diff_now' => 'núna', + 'diff_yesterday' => 'í gær', + 'diff_tomorrow' => 'á morgun', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM [kl.] HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'weekdays' => ['sunnudaginn', 'mánudaginn', 'þriðjudaginn', 'miðvikudaginn', 'fimmtudaginn', 'föstudaginn', 'laugardaginn'], + 'weekdays_short' => ['sun', 'mán', 'þri', 'mið', 'fim', 'fös', 'lau'], + 'weekdays_min' => ['sun', 'mán', 'þri', 'mið', 'fim', 'fös', 'lau'], + 'months' => ['janúar', 'febrúar', 'mars', 'apríl', 'maí', 'júní', 'júlí', 'ágúst', 'september', 'október', 'nóvember', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maí', 'jún', 'júl', 'ágú', 'sep', 'okt', 'nóv', 'des'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/is_IS.php b/libraries/Carbon/src/Carbon/Lang/is_IS.php new file mode 100644 index 00000000000..0a593d32aae --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/is_IS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/is.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/it.php b/libraries/Carbon/src/Carbon/Lang/it.php new file mode 100644 index 00000000000..b3911faece5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/it.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ash + * - François B + * - Marco Perrando + * - Massimiliano Caniparoli + * - JD Isaacks + * - Andrea Martini + * - Francesco Marasco + * - Tizianoz93 + * - Davide Casiraghi (davide-casiraghi) + * - Pete Scopes (pdscopes) + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count anno|:count anni', + 'a_year' => 'un anno|:count anni', + 'y' => ':count anno|:count anni', + 'month' => ':count mese|:count mesi', + 'a_month' => 'un mese|:count mesi', + 'm' => ':count mese|:count mesi', + 'week' => ':count settimana|:count settimane', + 'a_week' => 'una settimana|:count settimane', + 'w' => ':count set.', + 'day' => ':count giorno|:count giorni', + 'a_day' => 'un giorno|:count giorni', + 'd' => ':count g|:count gg', + 'hour' => ':count ora|:count ore', + 'a_hour' => 'un\'ora|:count ore', + 'h' => ':count h', + 'minute' => ':count minuto|:count minuti', + 'a_minute' => 'un minuto|:count minuti', + 'min' => ':count min.', + 'second' => ':count secondo|:count secondi', + 'a_second' => 'alcuni secondi|:count secondi', + 's' => ':count sec.', + 'millisecond' => ':count millisecondo|:count millisecondi', + 'a_millisecond' => 'un millisecondo|:count millisecondi', + 'ms' => ':countms', + 'microsecond' => ':count microsecondo|:count microsecondi', + 'a_microsecond' => 'un microsecondo|:count microsecondi', + 'µs' => ':countµs', + 'ago' => ':time fa', + 'from_now' => function ($time) { + return (preg_match('/^\d.+$/', $time) ? 'tra' : 'in')." $time"; + }, + 'after' => ':time dopo', + 'before' => ':time prima', + 'diff_now' => 'proprio ora', + 'diff_today' => 'Oggi', + 'diff_today_regexp' => 'Oggi(?:\\s+alle)?', + 'diff_yesterday' => 'ieri', + 'diff_yesterday_regexp' => 'Ieri(?:\\s+alle)?', + 'diff_tomorrow' => 'domani', + 'diff_tomorrow_regexp' => 'Domani(?:\\s+alle)?', + 'diff_before_yesterday' => 'l\'altro ieri', + 'diff_after_tomorrow' => 'dopodomani', + 'period_interval' => 'ogni :interval', + 'period_start_date' => 'dal :date', + 'period_end_date' => 'al :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Oggi alle] LT', + 'nextDay' => '[Domani alle] LT', + 'nextWeek' => 'dddd [alle] LT', + 'lastDay' => '[Ieri alle] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[la scorsa] dddd [alle] LT'; + default: + return '[lo scorso] dddd [alle] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'], + 'months_short' => ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], + 'weekdays' => ['domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], + 'weekdays_min' => ['do', 'lu', 'ma', 'me', 'gi', 've', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'ordinal_words' => [ + 'of' => 'di', + 'first' => 'primo', + 'second' => 'secondo', + 'third' => 'terzo', + 'fourth' => 'quarto', + 'fifth' => 'quinto', + 'last' => 'ultimo', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/it_CH.php b/libraries/Carbon/src/Carbon/Lang/it_CH.php new file mode 100644 index 00000000000..10cc82e64d4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/it_CH.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Propaganistas + */ +return array_replace_recursive(require __DIR__.'/it.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/it_IT.php b/libraries/Carbon/src/Carbon/Lang/it_IT.php new file mode 100644 index 00000000000..16af40a1ab3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return require __DIR__.'/it.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/it_SM.php b/libraries/Carbon/src/Carbon/Lang/it_SM.php new file mode 100644 index 00000000000..82e738f84cc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/it_SM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/it.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/it_VA.php b/libraries/Carbon/src/Carbon/Lang/it_VA.php new file mode 100644 index 00000000000..82e738f84cc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/it_VA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/it.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/iu.php b/libraries/Carbon/src/Carbon/Lang/iu.php new file mode 100644 index 00000000000..797ce5d3108 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/iu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/iu_CA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/iu_CA.php b/libraries/Carbon/src/Carbon/Lang/iu_CA.php new file mode 100644 index 00000000000..4c71108b1e2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/iu_CA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YY', + ], + 'months' => ['ᔮᓄᐊᓕ', 'ᕕᕗᐊᓕ', 'ᒪᔅᓯ', 'ᐃᐳᓗ', 'ᒪᐃ', 'ᔪᓂ', 'ᔪᓚᐃ', 'ᐊᒋᓯ', 'ᓯᑎᕙ', 'ᐊᑦᑐᕙ', 'ᓄᕕᕙ', 'ᑎᓯᕝᕙ'], + 'months_short' => ['ᔮᓄ', 'ᕕᕗ', 'ᒪᔅ', 'ᐃᐳ', 'ᒪᐃ', 'ᔪᓂ', 'ᔪᓚ', 'ᐊᒋ', 'ᓯᑎ', 'ᐊᑦ', 'ᓄᕕ', 'ᑎᓯ'], + 'weekdays' => ['ᓈᑦᑎᖑᔭᕐᕕᒃ', 'ᓇᒡᒐᔾᔭᐅ', 'ᓇᒡᒐᔾᔭᐅᓕᖅᑭᑦ', 'ᐱᖓᓲᓕᖅᓯᐅᑦ', 'ᕿᑎᖅᑰᑦ', 'ᐅᓪᓗᕈᓘᑐᐃᓇᖅ', 'ᓯᕙᑖᕕᒃ'], + 'weekdays_short' => ['ᓈ', 'ᓇ', 'ᓕ', 'ᐱ', 'ᕿ', 'ᐅ', 'ᓯ'], + 'weekdays_min' => ['ᓈ', 'ᓇ', 'ᓕ', 'ᐱ', 'ᕿ', 'ᐅ', 'ᓯ'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ᐅᑭᐅᖅ', + 'y' => ':count ᐅᑭᐅᖅ', + 'a_year' => ':count ᐅᑭᐅᖅ', + + 'month' => ':count qaammat', + 'm' => ':count qaammat', + 'a_month' => ':count qaammat', + + 'week' => ':count sapaatip akunnera', + 'w' => ':count sapaatip akunnera', + 'a_week' => ':count sapaatip akunnera', + + 'day' => ':count ulloq', + 'd' => ':count ulloq', + 'a_day' => ':count ulloq', + + 'hour' => ':count ikarraq', + 'h' => ':count ikarraq', + 'a_hour' => ':count ikarraq', + + 'minute' => ':count titiqqaralaaq', // less reliable + 'min' => ':count titiqqaralaaq', // less reliable + 'a_minute' => ':count titiqqaralaaq', // less reliable + + 'second' => ':count marluk', // less reliable + 's' => ':count marluk', // less reliable + 'a_second' => ':count marluk', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/iw.php b/libraries/Carbon/src/Carbon/Lang/iw.php new file mode 100644 index 00000000000..85b5eb57708 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/iw.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], + 'months_short' => ['ינו׳', 'פבר׳', 'מרץ', 'אפר׳', 'מאי', 'יוני', 'יולי', 'אוג׳', 'ספט׳', 'אוק׳', 'נוב׳', 'דצמ׳'], + 'weekdays' => ['יום ראשון', 'יום שני', 'יום שלישי', 'יום רביעי', 'יום חמישי', 'יום שישי', 'יום שבת'], + 'weekdays_short' => ['יום א׳', 'יום ב׳', 'יום ג׳', 'יום ד׳', 'יום ה׳', 'יום ו׳', 'שבת'], + 'weekdays_min' => ['א׳', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳'], + 'meridiem' => ['לפנה״צ', 'אחה״צ'], + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.M.YYYY', + 'LL' => 'D בMMM YYYY', + 'LLL' => 'D בMMMM YYYY H:mm', + 'LLLL' => 'dddd, D בMMMM YYYY H:mm', + ], + + 'year' => ':count שנה', + 'y' => ':count שנה', + 'a_year' => ':count שנה', + + 'month' => ':count חודש', + 'm' => ':count חודש', + 'a_month' => ':count חודש', + + 'week' => ':count שבוע', + 'w' => ':count שבוע', + 'a_week' => ':count שבוע', + + 'day' => ':count יום', + 'd' => ':count יום', + 'a_day' => ':count יום', + + 'hour' => ':count שעה', + 'h' => ':count שעה', + 'a_hour' => ':count שעה', + + 'minute' => ':count דקה', + 'min' => ':count דקה', + 'a_minute' => ':count דקה', + + 'second' => ':count שניה', + 's' => ':count שניה', + 'a_second' => ':count שניה', + + 'ago' => 'לפני :time', + 'from_now' => 'בעוד :time', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ja.php b/libraries/Carbon/src/Carbon/Lang/ja.php new file mode 100644 index 00000000000..e8eaa57ce5a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ja.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Takuya Sawada + * - Atsushi Tanaka + * - François B + * - Jason Katz-Brown + * - Serhan Apaydın + * - XueWei + * - JD Isaacks + * - toyama satoshi + * - atakigawa + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count年', + 'y' => ':count年', + 'month' => ':countヶ月', + 'm' => ':countヶ月', + 'week' => ':count週間', + 'w' => ':count週間', + 'day' => ':count日', + 'd' => ':count日', + 'hour' => ':count時間', + 'h' => ':count時間', + 'minute' => ':count分', + 'min' => ':count分', + 'second' => ':count秒', + 'a_second' => '{1}数秒|]1,Inf[:count秒', + 's' => ':count秒', + 'ago' => ':time前', + 'from_now' => ':time後', + 'after' => ':time後', + 'before' => ':time前', + 'diff_now' => '今', + 'diff_today' => '今日', + 'diff_yesterday' => '昨日', + 'diff_tomorrow' => '明日', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日 dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今日] LT', + 'nextDay' => '[明日] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($other->week !== $current->week) { + return '[来週]dddd LT'; + } + + return 'dddd LT'; + }, + 'lastDay' => '[昨日] LT', + 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($other->week !== $current->week) { + return '[先週]dddd LT'; + } + + return 'dddd LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + default: + return $number; + } + }, + 'meridiem' => ['午前', '午後'], + 'months' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'], + 'weekdays_short' => ['日', '月', '火', '水', '木', '金', '土'], + 'weekdays_min' => ['日', '月', '火', '水', '木', '金', '土'], + 'list' => '、', + 'alt_numbers' => ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十', '二十一', '二十二', '二十三', '二十四', '二十五', '二十六', '二十七', '二十八', '二十九', '三十', '三十一', '三十二', '三十三', '三十四', '三十五', '三十六', '三十七', '三十八', '三十九', '四十', '四十一', '四十二', '四十三', '四十四', '四十五', '四十六', '四十七', '四十八', '四十九', '五十', '五十一', '五十二', '五十三', '五十四', '五十五', '五十六', '五十七', '五十八', '五十九', '六十', '六十一', '六十二', '六十三', '六十四', '六十五', '六十六', '六十七', '六十八', '六十九', '七十', '七十一', '七十二', '七十三', '七十四', '七十五', '七十六', '七十七', '七十八', '七十九', '八十', '八十一', '八十二', '八十三', '八十四', '八十五', '八十六', '八十七', '八十八', '八十九', '九十', '九十一', '九十二', '九十三', '九十四', '九十五', '九十六', '九十七', '九十八', '九十九'], + 'alt_numbers_pow' => [ + 10000 => '万', + 1000 => '千', + 100 => '百', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ja_JP.php b/libraries/Carbon/src/Carbon/Lang/ja_JP.php new file mode 100644 index 00000000000..6bfdb170863 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ja_JP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ja.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/jgo.php b/libraries/Carbon/src/Carbon/Lang/jgo.php new file mode 100644 index 00000000000..73b22d685f8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/jgo.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/jmc.php b/libraries/Carbon/src/Carbon/Lang/jmc.php new file mode 100644 index 00000000000..f11ba53eaa9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/jmc.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/jv.php b/libraries/Carbon/src/Carbon/Lang/jv.php new file mode 100644 index 00000000000..06f92619c57 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/jv.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - tgfjt + * - JD Isaacks + */ +return [ + 'year' => '{1}setaun|]1,Inf[:count taun', + 'month' => '{1}sewulan|]1,Inf[:count wulan', + 'week' => '{1}sakminggu|]1,Inf[:count minggu', + 'day' => '{1}sedinten|]1,Inf[:count dinten', + 'hour' => '{1}setunggal jam|]1,Inf[:count jam', + 'minute' => '{1}setunggal menit|]1,Inf[:count menit', + 'second' => '{1}sawetawis detik|]1,Inf[:count detik', + 'ago' => ':time ingkang kepengker', + 'from_now' => 'wonten ing :time', + 'diff_today' => 'Dinten', + 'diff_yesterday' => 'Kala', + 'diff_yesterday_regexp' => 'Kala(?:\\s+wingi)?(?:\\s+pukul)?', + 'diff_tomorrow' => 'Mbenjang', + 'diff_tomorrow_regexp' => 'Mbenjang(?:\\s+pukul)?', + 'diff_today_regexp' => 'Dinten(?:\\s+puniko)?(?:\\s+pukul)?', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Dinten puniko pukul] LT', + 'nextDay' => '[Mbenjang pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kala wingi pukul] LT', + 'lastWeek' => 'dddd [kepengker pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'enjing'; + } + if ($hour < 15) { + return 'siyang'; + } + if ($hour < 19) { + return 'sonten'; + } + + return 'ndalu'; + }, + 'months' => ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'Nopember', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nop', 'Des'], + 'weekdays' => ['Minggu', 'Senen', 'Seloso', 'Rebu', 'Kemis', 'Jemuwah', 'Septu'], + 'weekdays_short' => ['Min', 'Sen', 'Sel', 'Reb', 'Kem', 'Jem', 'Sep'], + 'weekdays_min' => ['Mg', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sp'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' lan '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ka.php b/libraries/Carbon/src/Carbon/Lang/ka.php new file mode 100644 index 00000000000..2948361abdc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ka.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Tornike Razmadze + * - François B + * - Lasha Dolidze + * - Tim Fish + * - JD Isaacks + * - Tornike Razmadze + * - François B + * - Lasha Dolidze + * - JD Isaacks + * - LONGMAN + * - Avtandil Kikabidze (akalongman) + * - Levan Velijanashvili (Stichoza) + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count წელი', + 'y' => ':count წელი', + 'a_year' => '{1}წელი|]1,Inf[:count წელი', + 'month' => ':count თვე', + 'm' => ':count თვე', + 'a_month' => '{1}თვე|]1,Inf[:count თვე', + 'week' => ':count კვირა', + 'w' => ':count კვირა', + 'a_week' => '{1}კვირა|]1,Inf[:count კვირა', + 'day' => ':count დღე', + 'd' => ':count დღე', + 'a_day' => '{1}დღე|]1,Inf[:count დღე', + 'hour' => ':count საათი', + 'h' => ':count საათი', + 'a_hour' => '{1}საათი|]1,Inf[:count საათი', + 'minute' => ':count წუთი', + 'min' => ':count წუთი', + 'a_minute' => '{1}წუთი|]1,Inf[:count წუთი', + 'second' => ':count წამი', + 's' => ':count წამი', + 'a_second' => '{1}რამდენიმე წამი|]1,Inf[:count წამი', + 'ago' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლის', + // month + 'თვე' => 'თვის', + // week + 'კვირა' => 'კვირის', + // day + 'დღე' => 'დღის', + // hour + 'საათი' => 'საათის', + // minute + 'წუთი' => 'წუთის', + // second + 'წამი' => 'წამის', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time წინ"; + }, + 'from_now' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წელიწადში', + // week + 'კვირა' => 'კვირაში', + // day + 'დღე' => 'დღეში', + // month + 'თვე' => 'თვეში', + // hour + 'საათი' => 'საათში', + // minute + 'წუთი' => 'წუთში', + // second + 'წამი' => 'წამში', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return $time; + }, + 'after' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლის', + // month + 'თვე' => 'თვის', + // week + 'კვირა' => 'კვირის', + // day + 'დღე' => 'დღის', + // hour + 'საათი' => 'საათის', + // minute + 'წუთი' => 'წუთის', + // second + 'წამი' => 'წამის', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time შემდეგ"; + }, + 'before' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლით', + // month + 'თვე' => 'თვით', + // week + 'კვირა' => 'კვირით', + // day + 'დღე' => 'დღით', + // hour + 'საათი' => 'საათით', + // minute + 'წუთი' => 'წუთით', + // second + 'წამი' => 'წამით', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time ადრე"; + }, + 'diff_now' => 'ახლა', + 'diff_today' => 'დღეს', + 'diff_yesterday' => 'გუშინ', + 'diff_tomorrow' => 'ხვალ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[დღეს], LT[-ზე]', + 'nextDay' => '[ხვალ], LT[-ზე]', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + return ($current->isSameWeek($other) ? '' : '[შემდეგ] ').'dddd, LT[-ზე]'; + }, + 'lastDay' => '[გუშინ], LT[-ზე]', + 'lastWeek' => '[წინა] dddd, LT-ზე', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { + return $number; + } + if ($number === 1) { + return $number.'-ლი'; + } + if (($number < 20) || ($number <= 100 && ($number % 20 === 0)) || ($number % 100 === 0)) { + return 'მე-'.$number; + } + + return $number.'-ე'; + }, + 'months' => ['იანვარი', 'თებერვალი', 'მარტი', 'აპრილი', 'მაისი', 'ივნისი', 'ივლისი', 'აგვისტო', 'სექტემბერი', 'ოქტომბერი', 'ნოემბერი', 'დეკემბერი'], + 'months_standalone' => ['იანვარს', 'თებერვალს', 'მარტს', 'აპრილს', 'მაისს', 'ივნისს', 'ივლისს', 'აგვისტოს', 'სექტემბერს', 'ოქტომბერს', 'ნოემბერს', 'დეკემბერს'], + 'months_short' => ['იან', 'თებ', 'მარ', 'აპრ', 'მაი', 'ივნ', 'ივლ', 'აგვ', 'სექ', 'ოქტ', 'ნოე', 'დეკ'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['კვირას', 'ორშაბათს', 'სამშაბათს', 'ოთხშაბათს', 'ხუთშაბათს', 'პარასკევს', 'შაბათს'], + 'weekdays_standalone' => ['კვირა', 'ორშაბათი', 'სამშაბათი', 'ოთხშაბათი', 'ხუთშაბათი', 'პარასკევი', 'შაბათი'], + 'weekdays_short' => ['კვი', 'ორშ', 'სამ', 'ოთხ', 'ხუთ', 'პარ', 'შაბ'], + 'weekdays_min' => ['კვ', 'ორ', 'სა', 'ოთ', 'ხუ', 'პა', 'შა'], + 'weekdays_regexp' => '/^([^d].*|.*[^d])$/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' და '], + 'meridiem' => function ($hour) { + if ($hour >= 4) { + if ($hour < 11) { + return 'დილის'; + } + + if ($hour < 16) { + return 'შუადღის'; + } + + if ($hour < 22) { + return 'საღამოს'; + } + } + + return 'ღამის'; + }, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ka_GE.php b/libraries/Carbon/src/Carbon/Lang/ka_GE.php new file mode 100644 index 00000000000..97540e5bdc9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ka_GE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ka.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kab.php b/libraries/Carbon/src/Carbon/Lang/kab.php new file mode 100644 index 00000000000..b09a0313254 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kab.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kab_DZ.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kab_DZ.php b/libraries/Carbon/src/Carbon/Lang/kab_DZ.php new file mode 100644 index 00000000000..3227ada3a84 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kab_DZ.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - belkacem77@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Yennayer', 'Fuṛar', 'Meɣres', 'Yebrir', 'Mayyu', 'Yunyu', 'Yulyu', 'ɣuct', 'Ctembeṛ', 'Tubeṛ', 'Wambeṛ', 'Dujembeṛ'], + 'months_short' => ['Yen', 'Fur', 'Meɣ', 'Yeb', 'May', 'Yun', 'Yul', 'ɣuc', 'Cte', 'Tub', 'Wam', 'Duj'], + 'weekdays' => ['Acer', 'Arim', 'Aram', 'Ahad', 'Amhad', 'Sem', 'Sed'], + 'weekdays_short' => ['Ace', 'Ari', 'Ara', 'Aha', 'Amh', 'Sem', 'Sed'], + 'weekdays_min' => ['Ace', 'Ari', 'Ara', 'Aha', 'Amh', 'Sem', 'Sed'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['FT', 'MD'], + + 'year' => ':count n yiseggasen', + 'y' => ':count n yiseggasen', + 'a_year' => ':count n yiseggasen', + + 'month' => ':count n wayyuren', + 'm' => ':count n wayyuren', + 'a_month' => ':count n wayyuren', + + 'week' => ':count n ledwaṛ', // less reliable + 'w' => ':count n ledwaṛ', // less reliable + 'a_week' => ':count n ledwaṛ', // less reliable + + 'day' => ':count n wussan', + 'd' => ':count n wussan', + 'a_day' => ':count n wussan', + + 'hour' => ':count n tsaɛtin', + 'h' => ':count n tsaɛtin', + 'a_hour' => ':count n tsaɛtin', + + 'minute' => ':count n tedqiqin', + 'min' => ':count n tedqiqin', + 'a_minute' => ':count n tedqiqin', + + 'second' => ':count tasdidt', // less reliable + 's' => ':count tasdidt', // less reliable + 'a_second' => ':count tasdidt', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/kam.php b/libraries/Carbon/src/Carbon/Lang/kam.php new file mode 100644 index 00000000000..5a7a973f58d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kam.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ĩyakwakya', 'Ĩyawĩoo'], + 'weekdays' => ['Wa kyumwa', 'Wa kwambĩlĩlya', 'Wa kelĩ', 'Wa katatũ', 'Wa kana', 'Wa katano', 'Wa thanthatũ'], + 'weekdays_short' => ['Wky', 'Wkw', 'Wkl', 'Wtũ', 'Wkn', 'Wtn', 'Wth'], + 'weekdays_min' => ['Wky', 'Wkw', 'Wkl', 'Wtũ', 'Wkn', 'Wtn', 'Wth'], + 'months' => ['Mwai wa mbee', 'Mwai wa kelĩ', 'Mwai wa katatũ', 'Mwai wa kana', 'Mwai wa katano', 'Mwai wa thanthatũ', 'Mwai wa muonza', 'Mwai wa nyaanya', 'Mwai wa kenda', 'Mwai wa ĩkumi', 'Mwai wa ĩkumi na ĩmwe', 'Mwai wa ĩkumi na ilĩ'], + 'months_short' => ['Mbe', 'Kel', 'Ktũ', 'Kan', 'Ktn', 'Tha', 'Moo', 'Nya', 'Knd', 'Ĩku', 'Ĩkm', 'Ĩkl'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count mbua', // less reliable + 'y' => ':count mbua', // less reliable + 'a_year' => ':count mbua', // less reliable + + 'month' => ':count ndakitali', // less reliable + 'm' => ':count ndakitali', // less reliable + 'a_month' => ':count ndakitali', // less reliable + + 'day' => ':count wia', // less reliable + 'd' => ':count wia', // less reliable + 'a_day' => ':count wia', // less reliable + + 'hour' => ':count orasan', // less reliable + 'h' => ':count orasan', // less reliable + 'a_hour' => ':count orasan', // less reliable + + 'minute' => ':count orasan', // less reliable + 'min' => ':count orasan', // less reliable + 'a_minute' => ':count orasan', // less reliable + */ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/kde.php b/libraries/Carbon/src/Carbon/Lang/kde.php new file mode 100644 index 00000000000..78e3a4b1a7c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kde.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Muhi', 'Chilo'], + 'weekdays' => ['Liduva lyapili', 'Liduva lyatatu', 'Liduva lyanchechi', 'Liduva lyannyano', 'Liduva lyannyano na linji', 'Liduva lyannyano na mavili', 'Liduva litandi'], + 'weekdays_short' => ['Ll2', 'Ll3', 'Ll4', 'Ll5', 'Ll6', 'Ll7', 'Ll1'], + 'weekdays_min' => ['Ll2', 'Ll3', 'Ll4', 'Ll5', 'Ll6', 'Ll7', 'Ll1'], + 'months' => ['Mwedi Ntandi', 'Mwedi wa Pili', 'Mwedi wa Tatu', 'Mwedi wa Nchechi', 'Mwedi wa Nnyano', 'Mwedi wa Nnyano na Umo', 'Mwedi wa Nnyano na Mivili', 'Mwedi wa Nnyano na Mitatu', 'Mwedi wa Nnyano na Nchechi', 'Mwedi wa Nnyano na Nnyano', 'Mwedi wa Nnyano na Nnyano na U', 'Mwedi wa Nnyano na Nnyano na M'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/kea.php b/libraries/Carbon/src/Carbon/Lang/kea.php new file mode 100644 index 00000000000..d1b0d03e1c3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kea.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['dumingu', 'sigunda-fera', 'tersa-fera', 'kuarta-fera', 'kinta-fera', 'sesta-fera', 'sabadu'], + 'weekdays_short' => ['dum', 'sig', 'ter', 'kua', 'kin', 'ses', 'sab'], + 'weekdays_min' => ['du', 'si', 'te', 'ku', 'ki', 'se', 'sa'], + 'weekdays_standalone' => ['dumingu', 'sigunda-fera', 'tersa-fera', 'kuarta-fera', 'kinta-fera', 'sesta-fera', 'sábadu'], + 'months' => ['Janeru', 'Febreru', 'Marsu', 'Abril', 'Maiu', 'Junhu', 'Julhu', 'Agostu', 'Setenbru', 'Otubru', 'Nuvenbru', 'Dizenbru'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Otu', 'Nuv', 'Diz'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D [di] MMMM [di] YYYY HH:mm', + 'LLLL' => 'dddd, D [di] MMMM [di] YYYY HH:mm', + ], + + 'year' => ':count otunu', // less reliable + 'y' => ':count otunu', // less reliable + 'a_year' => ':count otunu', // less reliable + + 'week' => ':count día dumingu', // less reliable + 'w' => ':count día dumingu', // less reliable + 'a_week' => ':count día dumingu', // less reliable + + 'day' => ':count diâ', // less reliable + 'd' => ':count diâ', // less reliable + 'a_day' => ':count diâ', // less reliable + + 'minute' => ':count sugundu', // less reliable + 'min' => ':count sugundu', // less reliable + 'a_minute' => ':count sugundu', // less reliable + + 'second' => ':count dós', // less reliable + 's' => ':count dós', // less reliable + 'a_second' => ':count dós', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/khq.php b/libraries/Carbon/src/Carbon/Lang/khq.php new file mode 100644 index 00000000000..27b4a3c942c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/khq.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Adduha', 'Aluula'], + 'weekdays' => ['Alhadi', 'Atini', 'Atalata', 'Alarba', 'Alhamiisa', 'Aljuma', 'Assabdu'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alj', 'Ass'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alj', 'Ass'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ki.php b/libraries/Carbon/src/Carbon/Lang/ki.php new file mode 100644 index 00000000000..a2993f9f5d9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ki.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Kiroko', 'Hwaĩ-inĩ'], + 'weekdays' => ['Kiumia', 'Njumatatũ', 'Njumaine', 'Njumatana', 'Aramithi', 'Njumaa', 'Njumamothi'], + 'weekdays_short' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], + 'weekdays_min' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], + 'months' => ['Njenuarĩ', 'Mwere wa kerĩ', 'Mwere wa gatatũ', 'Mwere wa kana', 'Mwere wa gatano', 'Mwere wa gatandatũ', 'Mwere wa mũgwanja', 'Mwere wa kanana', 'Mwere wa kenda', 'Mwere wa ikũmi', 'Mwere wa ikũmi na ũmwe', 'Ndithemba'], + 'months_short' => ['JEN', 'WKR', 'WGT', 'WKN', 'WTN', 'WTD', 'WMJ', 'WNN', 'WKD', 'WIK', 'WMW', 'DIT'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count mĩaka', // less reliable + 'y' => ':count mĩaka', // less reliable + 'a_year' => ':count mĩaka', // less reliable + + 'month' => ':count mweri', // less reliable + 'm' => ':count mweri', // less reliable + 'a_month' => ':count mweri', // less reliable + + 'week' => ':count kiumia', // less reliable + 'w' => ':count kiumia', // less reliable + 'a_week' => ':count kiumia', // less reliable + + 'day' => ':count mũthenya', // less reliable + 'd' => ':count mũthenya', // less reliable + 'a_day' => ':count mũthenya', // less reliable + + 'hour' => ':count thaa', // less reliable + 'h' => ':count thaa', // less reliable + 'a_hour' => ':count thaa', // less reliable + + 'minute' => ':count mundu', // less reliable + 'min' => ':count mundu', // less reliable + 'a_minute' => ':count mundu', // less reliable + + 'second' => ':count igego', // less reliable + 's' => ':count igego', // less reliable + 'a_second' => ':count igego', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/kk.php b/libraries/Carbon/src/Carbon/Lang/kk.php new file mode 100644 index 00000000000..a3da4305e1c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kk.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Talat Uspanov + * - Нурлан Рахимжанов + * - Toleugazy Kali + */ +return [ + 'year' => ':count жыл', + 'a_year' => '{1}бір жыл|:count жыл', + 'y' => ':count ж.', + 'month' => ':count ай', + 'a_month' => '{1}бір ай|:count ай', + 'm' => ':count ай', + 'week' => ':count апта', + 'a_week' => '{1}бір апта', + 'w' => ':count ап.', + 'day' => ':count күн', + 'a_day' => '{1}бір күн|:count күн', + 'd' => ':count к.', + 'hour' => ':count сағат', + 'a_hour' => '{1}бір сағат|:count сағат', + 'h' => ':count са.', + 'minute' => ':count минут', + 'a_minute' => '{1}бір минут|:count минут', + 'min' => ':count м.', + 'second' => ':count секунд', + 'a_second' => '{1}бірнеше секунд|:count секунд', + 's' => ':count се.', + 'ago' => ':time бұрын', + 'from_now' => ':time ішінде', + 'after' => ':time кейін', + 'before' => ':time бұрын', + 'diff_now' => 'қазір', + 'diff_today' => 'Бүгін', + 'diff_today_regexp' => 'Бүгін(?:\\s+сағат)?', + 'diff_yesterday' => 'кеше', + 'diff_yesterday_regexp' => 'Кеше(?:\\s+сағат)?', + 'diff_tomorrow' => 'ертең', + 'diff_tomorrow_regexp' => 'Ертең(?:\\s+сағат)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бүгін сағат] LT', + 'nextDay' => '[Ертең сағат] LT', + 'nextWeek' => 'dddd [сағат] LT', + 'lastDay' => '[Кеше сағат] LT', + 'lastWeek' => '[Өткен аптаның] dddd [сағат] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + static $suffixes = [ + 0 => '-ші', + 1 => '-ші', + 2 => '-ші', + 3 => '-ші', + 4 => '-ші', + 5 => '-ші', + 6 => '-шы', + 7 => '-ші', + 8 => '-ші', + 9 => '-шы', + 10 => '-шы', + 20 => '-шы', + 30 => '-шы', + 40 => '-шы', + 50 => '-ші', + 60 => '-шы', + 70 => '-ші', + 80 => '-ші', + 90 => '-шы', + 100 => '-ші', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'months' => ['қаңтар', 'ақпан', 'наурыз', 'сәуір', 'мамыр', 'маусым', 'шілде', 'тамыз', 'қыркүйек', 'қазан', 'қараша', 'желтоқсан'], + 'months_short' => ['қаң', 'ақп', 'нау', 'сәу', 'мам', 'мау', 'шіл', 'там', 'қыр', 'қаз', 'қар', 'жел'], + 'weekdays' => ['жексенбі', 'дүйсенбі', 'сейсенбі', 'сәрсенбі', 'бейсенбі', 'жұма', 'сенбі'], + 'weekdays_short' => ['жек', 'дүй', 'сей', 'сәр', 'бей', 'жұм', 'сен'], + 'weekdays_min' => ['жк', 'дй', 'сй', 'ср', 'бй', 'жм', 'сн'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' және '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/kk_KZ.php b/libraries/Carbon/src/Carbon/Lang/kk_KZ.php new file mode 100644 index 00000000000..257ff904de3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kk_KZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/kk.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kkj.php b/libraries/Carbon/src/Carbon/Lang/kkj.php new file mode 100644 index 00000000000..73b22d685f8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kkj.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/kl.php b/libraries/Carbon/src/Carbon/Lang/kl.php new file mode 100644 index 00000000000..63d87154fbe --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kl.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kl_GL.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kl_GL.php b/libraries/Carbon/src/Carbon/Lang/kl_GL.php new file mode 100644 index 00000000000..e62fde5e441 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kl_GL.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Danish Standards Association bug-glibc-locales@gnu.org + * - John Eyðstein Johannesen (mashema) + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd [d.] D. MMMM YYYY [kl.] HH:mm', + ], + 'months' => ['januaarip', 'februaarip', 'marsip', 'apriilip', 'maajip', 'juunip', 'juulip', 'aggustip', 'septembarip', 'oktobarip', 'novembarip', 'decembarip'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['sapaat', 'ataasinngorneq', 'marlunngorneq', 'pingasunngorneq', 'sisamanngorneq', 'tallimanngorneq', 'arfininngorneq'], + 'weekdays_short' => ['sap', 'ata', 'mar', 'pin', 'sis', 'tal', 'arf'], + 'weekdays_min' => ['sap', 'ata', 'mar', 'pin', 'sis', 'tal', 'arf'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => '{1}ukioq :count|{0}:count ukiut|]1,Inf[ukiut :count', + 'a_year' => '{1}ukioq|{0}:count ukiut|]1,Inf[ukiut :count', + 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + + 'month' => '{1}qaammat :count|{0}:count qaammatit|]1,Inf[qaammatit :count', + 'a_month' => '{1}qaammat|{0}:count qaammatit|]1,Inf[qaammatit :count', + 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + + 'week' => '{1}:count sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'a_week' => '{1}a sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'w' => ':countw', + + 'day' => '{1}:count ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'a_day' => '{1}a ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'd' => ':countd', + + 'hour' => '{1}:count tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'a_hour' => '{1}tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'h' => ':counth', + + 'minute' => '{1}:count minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'a_minute' => '{1}a minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'min' => ':countm', + + 'second' => '{1}:count sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 'a_second' => '{1}sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 's' => ':counts', + + 'ago' => ':time matuma siorna', + +]); diff --git a/libraries/Carbon/src/Carbon/Lang/kln.php b/libraries/Carbon/src/Carbon/Lang/kln.php new file mode 100644 index 00000000000..77132e5c939 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kln.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['krn', 'koosk'], + 'weekdays' => ['Kotisap', 'Kotaai', 'Koaeng’', 'Kosomok', 'Koang’wan', 'Komuut', 'Kolo'], + 'weekdays_short' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], + 'weekdays_min' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], + 'months' => ['Mulgul', 'Ng’atyaato', 'Kiptaamo', 'Iwootkuut', 'Mamuut', 'Paagi', 'Ng’eiyeet', 'Rooptui', 'Bureet', 'Epeeso', 'Kipsuunde ne taai', 'Kipsuunde nebo aeng’'], + 'months_short' => ['Mul', 'Ngat', 'Taa', 'Iwo', 'Mam', 'Paa', 'Nge', 'Roo', 'Bur', 'Epe', 'Kpt', 'Kpa'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count maghatiat', // less reliable + 'y' => ':count maghatiat', // less reliable + 'a_year' => ':count maghatiat', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/km.php b/libraries/Carbon/src/Carbon/Lang/km.php new file mode 100644 index 00000000000..854da208c63 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/km.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kruy Vanna + * - Sereysethy Touch + * - JD Isaacks + * - Sovichet Tep + */ +return [ + 'year' => '{1}មួយឆ្នាំ|]1,Inf[:count ឆ្នាំ', + 'y' => ':count ឆ្នាំ', + 'month' => '{1}មួយខែ|]1,Inf[:count ខែ', + 'm' => ':count ខែ', + 'week' => ':count សប្ដាហ៍', + 'w' => ':count សប្ដាហ៍', + 'day' => '{1}មួយថ្ងៃ|]1,Inf[:count ថ្ងៃ', + 'd' => ':count ថ្ងៃ', + 'hour' => '{1}មួយម៉ោង|]1,Inf[:count ម៉ោង', + 'h' => ':count ម៉ោង', + 'minute' => '{1}មួយនាទី|]1,Inf[:count នាទី', + 'min' => ':count នាទី', + 'second' => '{1}ប៉ុន្មានវិនាទី|]1,Inf[:count វិនាទី', + 's' => ':count វិនាទី', + 'ago' => ':timeមុន', + 'from_now' => ':timeទៀត', + 'after' => 'នៅ​ក្រោយ :time', + 'before' => 'នៅ​មុន :time', + 'diff_now' => 'ឥឡូវ', + 'diff_today' => 'ថ្ងៃនេះ', + 'diff_today_regexp' => 'ថ្ងៃនេះ(?:\\s+ម៉ោង)?', + 'diff_yesterday' => 'ម្សិលមិញ', + 'diff_yesterday_regexp' => 'ម្សិលមិញ(?:\\s+ម៉ោង)?', + 'diff_tomorrow' => 'ថ្ងៃ​ស្អែក', + 'diff_tomorrow_regexp' => 'ស្អែក(?:\\s+ម៉ោង)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ថ្ងៃនេះ ម៉ោង] LT', + 'nextDay' => '[ស្អែក ម៉ោង] LT', + 'nextWeek' => 'dddd [ម៉ោង] LT', + 'lastDay' => '[ម្សិលមិញ ម៉ោង] LT', + 'lastWeek' => 'dddd [សប្តាហ៍មុន] [ម៉ោង] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ទី:number', + 'meridiem' => ['ព្រឹក', 'ល្ងាច'], + 'months' => ['មករា', 'កុម្ភៈ', 'មីនា', 'មេសា', 'ឧសភា', 'មិថុនា', 'កក្កដា', 'សីហា', 'កញ្ញា', 'តុលា', 'វិច្ឆិកា', 'ធ្នូ'], + 'months_short' => ['មករា', 'កុម្ភៈ', 'មីនា', 'មេសា', 'ឧសភា', 'មិថុនា', 'កក្កដា', 'សីហា', 'កញ្ញា', 'តុលា', 'វិច្ឆិកា', 'ធ្នូ'], + 'weekdays' => ['អាទិត្យ', 'ច័ន្ទ', 'អង្គារ', 'ពុធ', 'ព្រហស្បតិ៍', 'សុក្រ', 'សៅរ៍'], + 'weekdays_short' => ['អា', 'ច', 'អ', 'ព', 'ព្រ', 'សុ', 'ស'], + 'weekdays_min' => ['អា', 'ច', 'អ', 'ព', 'ព្រ', 'សុ', 'ស'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', 'និង '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/km_KH.php b/libraries/Carbon/src/Carbon/Lang/km_KH.php new file mode 100644 index 00000000000..812d0711dbe --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/km_KH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/km.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kn.php b/libraries/Carbon/src/Carbon/Lang/kn.php new file mode 100644 index 00000000000..2d6ea47dff5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kn.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - MOHAN M U + * - François B + * - rajeevnaikte + */ +return [ + 'year' => '{1}ಒಂದು ವರ್ಷ|]1,Inf[:count ವರ್ಷ', + 'month' => '{1}ಒಂದು ತಿಂಗಳು|]1,Inf[:count ತಿಂಗಳು', + 'week' => '{1}ಒಂದು ವಾರ|]1,Inf[:count ವಾರಗಳು', + 'day' => '{1}ಒಂದು ದಿನ|]1,Inf[:count ದಿನ', + 'hour' => '{1}ಒಂದು ಗಂಟೆ|]1,Inf[:count ಗಂಟೆ', + 'minute' => '{1}ಒಂದು ನಿಮಿಷ|]1,Inf[:count ನಿಮಿಷ', + 'second' => '{1}ಕೆಲವು ಕ್ಷಣಗಳು|]1,Inf[:count ಸೆಕೆಂಡುಗಳು', + 'ago' => ':time ಹಿಂದೆ', + 'from_now' => ':time ನಂತರ', + 'diff_now' => 'ಈಗ', + 'diff_today' => 'ಇಂದು', + 'diff_yesterday' => 'ನಿನ್ನೆ', + 'diff_tomorrow' => 'ನಾಳೆ', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[ಇಂದು] LT', + 'nextDay' => '[ನಾಳೆ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ನಿನ್ನೆ] LT', + 'lastWeek' => '[ಕೊನೆಯ] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberನೇ', + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ರಾತ್ರಿ'; + } + if ($hour < 10) { + return 'ಬೆಳಿಗ್ಗೆ'; + } + if ($hour < 17) { + return 'ಮಧ್ಯಾಹ್ನ'; + } + if ($hour < 20) { + return 'ಸಂಜೆ'; + } + + return 'ರಾತ್ರಿ'; + }, + 'months' => ['ಜನವರಿ', 'ಫೆಬ್ರವರಿ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂಬರ್', 'ಅಕ್ಟೋಬರ್', 'ನವೆಂಬರ್', 'ಡಿಸೆಂಬರ್'], + 'months_short' => ['ಜನ', 'ಫೆಬ್ರ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂ', 'ಅಕ್ಟೋ', 'ನವೆಂ', 'ಡಿಸೆಂ'], + 'weekdays' => ['ಭಾನುವಾರ', 'ಸೋಮವಾರ', 'ಮಂಗಳವಾರ', 'ಬುಧವಾರ', 'ಗುರುವಾರ', 'ಶುಕ್ರವಾರ', 'ಶನಿವಾರ'], + 'weekdays_short' => ['ಭಾನು', 'ಸೋಮ', 'ಮಂಗಳ', 'ಬುಧ', 'ಗುರು', 'ಶುಕ್ರ', 'ಶನಿ'], + 'weekdays_min' => ['ಭಾ', 'ಸೋ', 'ಮಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'list' => ', ', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/kn_IN.php b/libraries/Carbon/src/Carbon/Lang/kn_IN.php new file mode 100644 index 00000000000..add08d1502a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kn_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/kn.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ko.php b/libraries/Carbon/src/Carbon/Lang/ko.php new file mode 100644 index 00000000000..72e67ea74f7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ko.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - FourwingsY + * - François B + * - Jason Katz-Brown + * - Seokjun Kim + * - Junho Kim + * - JD Isaacks + * - Juwon Kim + */ +return [ + 'year' => ':count년', + 'a_year' => '{1}일년|]1,Inf[:count년', + 'y' => ':count년', + 'month' => ':count개월', + 'a_month' => '{1}한달|]1,Inf[:count개월', + 'm' => ':count개월', + 'week' => ':count주', + 'a_week' => '{1}일주일|]1,Inf[:count 주', + 'w' => ':count주일', + 'day' => ':count일', + 'a_day' => '{1}하루|]1,Inf[:count일', + 'd' => ':count일', + 'hour' => ':count시간', + 'a_hour' => '{1}한시간|]1,Inf[:count시간', + 'h' => ':count시간', + 'minute' => ':count분', + 'a_minute' => '{1}일분|]1,Inf[:count분', + 'min' => ':count분', + 'second' => ':count초', + 'a_second' => '{1}몇초|]1,Inf[:count초', + 's' => ':count초', + 'ago' => ':time 전', + 'from_now' => ':time 후', + 'after' => ':time 후', + 'before' => ':time 전', + 'diff_now' => '지금', + 'diff_today' => '오늘', + 'diff_yesterday' => '어제', + 'diff_tomorrow' => '내일', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'YYYY.MM.DD.', + 'LL' => 'YYYY년 MMMM D일', + 'LLL' => 'YYYY년 MMMM D일 A h:mm', + 'LLLL' => 'YYYY년 MMMM D일 dddd A h:mm', + ], + 'calendar' => [ + 'sameDay' => '오늘 LT', + 'nextDay' => '내일 LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '어제 LT', + 'lastWeek' => '지난주 dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'일'; + case 'M': + return $number.'월'; + case 'w': + case 'W': + return $number.'주'; + default: + return $number; + } + }, + 'meridiem' => ['오전', '오후'], + 'months' => ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], + 'months_short' => ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], + 'weekdays' => ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'], + 'weekdays_short' => ['일', '월', '화', '수', '목', '금', '토'], + 'weekdays_min' => ['일', '월', '화', '수', '목', '금', '토'], + 'list' => ' ', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ko_KP.php b/libraries/Carbon/src/Carbon/Lang/ko_KP.php new file mode 100644 index 00000000000..e956821de0c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ko_KP.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ko.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ko_KR.php b/libraries/Carbon/src/Carbon/Lang/ko_KR.php new file mode 100644 index 00000000000..88a8d8b399f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ko_KR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ko.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kok.php b/libraries/Carbon/src/Carbon/Lang/kok.php new file mode 100644 index 00000000000..11e9337c90e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kok.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kok_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kok_IN.php b/libraries/Carbon/src/Carbon/Lang/kok_IN.php new file mode 100644 index 00000000000..216f31c50df --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kok_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-M-YY', + ], + 'months' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ओगस्ट', 'सेप्टेंबर', 'ओक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'months_short' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ओगस्ट', 'सेप्टेंबर', 'ओक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'weekdays' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'weekdays_short' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'weekdays_min' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['म.पू.', 'म.नं.'], + + 'year' => ':count वैशाकु', // less reliable + 'y' => ':count वैशाकु', // less reliable + 'a_year' => ':count वैशाकु', // less reliable + + 'week' => ':count आदित्यवार', // less reliable + 'w' => ':count आदित्यवार', // less reliable + 'a_week' => ':count आदित्यवार', // less reliable + + 'minute' => ':count नोंद', // less reliable + 'min' => ':count नोंद', // less reliable + 'a_minute' => ':count नोंद', // less reliable + + 'second' => ':count तेंको', // less reliable + 's' => ':count तेंको', // less reliable + 'a_second' => ':count तेंको', // less reliable + + 'month' => ':count मैनो', + 'm' => ':count मैनो', + 'a_month' => ':count मैनो', + + 'day' => ':count दिवसु', + 'd' => ':count दिवसु', + 'a_day' => ':count दिवसु', + + 'hour' => ':count घंते', + 'h' => ':count घंते', + 'a_hour' => ':count घंते', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ks.php b/libraries/Carbon/src/Carbon/Lang/ks.php new file mode 100644 index 00000000000..573d8959854 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ks.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ks_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ks_IN.php b/libraries/Carbon/src/Carbon/Lang/ks_IN.php new file mode 100644 index 00000000000..3d6003cbe65 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ks_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['جنؤری', 'فرؤری', 'مارٕچ', 'اپریل', 'میٔ', 'جوٗن', 'جوٗلایی', 'اگست', 'ستمبر', 'اکتوٗبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنؤری', 'فرؤری', 'مارٕچ', 'اپریل', 'میٔ', 'جوٗن', 'جوٗلایی', 'اگست', 'ستمبر', 'اکتوٗبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['آتهوار', 'ژءندروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'weekdays_short' => ['آتهوار', 'ژءنتروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'weekdays_min' => ['آتهوار', 'ژءنتروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['دوپھربرونھ', 'دوپھرپتھ'], + + 'year' => ':count آب', // less reliable + 'y' => ':count آب', // less reliable + 'a_year' => ':count آب', // less reliable + + 'month' => ':count रान्', // less reliable + 'm' => ':count रान्', // less reliable + 'a_month' => ':count रान्', // less reliable + + 'week' => ':count آتھٕوار', // less reliable + 'w' => ':count آتھٕوار', // less reliable + 'a_week' => ':count آتھٕوار', // less reliable + + 'hour' => ':count سۄن', // less reliable + 'h' => ':count سۄن', // less reliable + 'a_hour' => ':count سۄن', // less reliable + + 'minute' => ':count فَن', // less reliable + 'min' => ':count فَن', // less reliable + 'a_minute' => ':count فَن', // less reliable + + 'second' => ':count दोʼयुम', // less reliable + 's' => ':count दोʼयुम', // less reliable + 'a_second' => ':count दोʼयुम', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ks_IN@devanagari.php b/libraries/Carbon/src/Carbon/Lang/ks_IN@devanagari.php new file mode 100644 index 00000000000..4f5d6f9afc2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ks_IN@devanagari.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ks-gnome-trans-commits@lists.code.indlinux.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['आथवार', 'चॅ़दुरवार', 'बोमवार', 'ब्वदवार', 'ब्रसवार', 'शोकुरवार', 'बटुवार'], + 'weekdays_short' => ['आथ ', 'चॅ़दुर', 'बोम', 'ब्वद', 'ब्रस', 'शोकुर', 'बटु'], + 'weekdays_min' => ['आथ ', 'चॅ़दुर', 'बोम', 'ब्वद', 'ब्रस', 'शोकुर', 'बटु'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ksb.php b/libraries/Carbon/src/Carbon/Lang/ksb.php new file mode 100644 index 00000000000..11dcb02203d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ksb.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['makeo', 'nyiaghuo'], + 'weekdays' => ['Jumaapii', 'Jumaatatu', 'Jumaane', 'Jumaatano', 'Alhamisi', 'Ijumaa', 'Jumaamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jmn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jmn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januali', 'Febluali', 'Machi', 'Aplili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ksf.php b/libraries/Carbon/src/Carbon/Lang/ksf.php new file mode 100644 index 00000000000..a5f63e7b41c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ksf.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['sárúwá', 'cɛɛ́nko'], + 'weekdays' => ['sɔ́ndǝ', 'lǝndí', 'maadí', 'mɛkrɛdí', 'jǝǝdí', 'júmbá', 'samdí'], + 'weekdays_short' => ['sɔ́n', 'lǝn', 'maa', 'mɛk', 'jǝǝ', 'júm', 'sam'], + 'weekdays_min' => ['sɔ́n', 'lǝn', 'maa', 'mɛk', 'jǝǝ', 'júm', 'sam'], + 'months' => ['ŋwíí a ntɔ́ntɔ', 'ŋwíí akǝ bɛ́ɛ', 'ŋwíí akǝ ráá', 'ŋwíí akǝ nin', 'ŋwíí akǝ táan', 'ŋwíí akǝ táafɔk', 'ŋwíí akǝ táabɛɛ', 'ŋwíí akǝ táaraa', 'ŋwíí akǝ táanin', 'ŋwíí akǝ ntɛk', 'ŋwíí akǝ ntɛk di bɔ́k', 'ŋwíí akǝ ntɛk di bɛ́ɛ'], + 'months_short' => ['ŋ1', 'ŋ2', 'ŋ3', 'ŋ4', 'ŋ5', 'ŋ6', 'ŋ7', 'ŋ8', 'ŋ9', 'ŋ10', 'ŋ11', 'ŋ12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ksh.php b/libraries/Carbon/src/Carbon/Lang/ksh.php new file mode 100644 index 00000000000..551aa14611e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ksh.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['v.M.', 'n.M.'], + 'weekdays' => ['Sunndaach', 'Mohndaach', 'Dinnsdaach', 'Metwoch', 'Dunnersdaach', 'Friidaach', 'Samsdaach'], + 'weekdays_short' => ['Su.', 'Mo.', 'Di.', 'Me.', 'Du.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['Su', 'Mo', 'Di', 'Me', 'Du', 'Fr', 'Sa'], + 'months' => ['Jannewa', 'Fäbrowa', 'Määz', 'Aprell', 'Mai', 'Juuni', 'Juuli', 'Oujoß', 'Septämber', 'Oktohber', 'Novämber', 'Dezämber'], + 'months_short' => ['Jan', 'Fäb', 'Mäz', 'Apr', 'Mai', 'Jun', 'Jul', 'Ouj', 'Säp', 'Okt', 'Nov', 'Dez'], + 'months_short_standalone' => ['Jan.', 'Fäb.', 'Mäz.', 'Apr.', 'Mai', 'Jun.', 'Jul.', 'Ouj.', 'Säp.', 'Okt.', 'Nov.', 'Dez.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D. M. YYYY', + 'LL' => 'D. MMM. YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, [dä] D. MMMM YYYY HH:mm', + ], + + 'year' => ':count Johr', + 'y' => ':count Johr', + 'a_year' => ':count Johr', + + 'month' => ':count Moohnd', + 'm' => ':count Moohnd', + 'a_month' => ':count Moohnd', + + 'week' => ':count woch', + 'w' => ':count woch', + 'a_week' => ':count woch', + + 'day' => ':count Daach', + 'd' => ':count Daach', + 'a_day' => ':count Daach', + + 'hour' => ':count Uhr', + 'h' => ':count Uhr', + 'a_hour' => ':count Uhr', + + 'minute' => ':count Menutt', + 'min' => ':count Menutt', + 'a_minute' => ':count Menutt', + + 'second' => ':count Sekůndt', + 's' => ':count Sekůndt', + 'a_second' => ':count Sekůndt', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ku.php b/libraries/Carbon/src/Carbon/Lang/ku.php new file mode 100644 index 00000000000..5468d0fc361 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ku.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Unicode, Inc. + */ + +return [ + 'ago' => 'berî :time', + 'from_now' => 'di :time de', + 'after' => ':time piştî', + 'before' => ':time berê', + 'year' => ':count sal', + 'year_ago' => ':count salê|:count salan', + 'year_from_now' => 'salekê|:count salan', + 'month' => ':count meh', + 'week' => ':count hefte', + 'day' => ':count roj', + 'hour' => ':count saet', + 'minute' => ':count deqîqe', + 'second' => ':count saniye', + 'months' => ['rêbendanê', 'reşemiyê', 'adarê', 'avrêlê', 'gulanê', 'pûşperê', 'tîrmehê', 'gelawêjê', 'rezberê', 'kewçêrê', 'sermawezê', 'berfanbarê'], + 'months_standalone' => ['rêbendan', 'reşemî', 'adar', 'avrêl', 'gulan', 'pûşper', 'tîrmeh', 'gelawêj', 'rezber', 'kewçêr', 'sermawez', 'berfanbar'], + 'months_short' => ['rêb', 'reş', 'ada', 'avr', 'gul', 'pûş', 'tîr', 'gel', 'rez', 'kew', 'ser', 'ber'], + 'weekdays' => ['yekşem', 'duşem', 'sêşem', 'çarşem', 'pêncşem', 'în', 'şemî'], + 'weekdays_short' => ['yş', 'dş', 'sş', 'çş', 'pş', 'în', 'ş'], + 'weekdays_min' => ['Y', 'D', 'S', 'Ç', 'P', 'Î', 'Ş'], + 'list' => [', ', ' û '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ku_TR.php b/libraries/Carbon/src/Carbon/Lang/ku_TR.php new file mode 100644 index 00000000000..10e6f76f0b8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ku_TR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ku.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kw.php b/libraries/Carbon/src/Carbon/Lang/kw.php new file mode 100644 index 00000000000..e83f501c0f1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kw_GB.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/kw_GB.php b/libraries/Carbon/src/Carbon/Lang/kw_GB.php new file mode 100644 index 00000000000..20f66a7252c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/kw_GB.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alastair McKinstry bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['mis Genver', 'mis Hwevrer', 'mis Meurth', 'mis Ebrel', 'mis Me', 'mis Metheven', 'mis Gortheren', 'mis Est', 'mis Gwynngala', 'mis Hedra', 'mis Du', 'mis Kevardhu'], + 'months_short' => ['Gen', 'Hwe', 'Meu', 'Ebr', 'Me', 'Met', 'Gor', 'Est', 'Gwn', 'Hed', 'Du', 'Kev'], + 'weekdays' => ['De Sul', 'De Lun', 'De Merth', 'De Merher', 'De Yow', 'De Gwener', 'De Sadorn'], + 'weekdays_short' => ['Sul', 'Lun', 'Mth', 'Mhr', 'Yow', 'Gwe', 'Sad'], + 'weekdays_min' => ['Sul', 'Lun', 'Mth', 'Mhr', 'Yow', 'Gwe', 'Sad'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count bledhen', + 'y' => ':count bledhen', + 'a_year' => ':count bledhen', + + 'month' => ':count mis', + 'm' => ':count mis', + 'a_month' => ':count mis', + + 'week' => ':count seythen', + 'w' => ':count seythen', + 'a_week' => ':count seythen', + + 'day' => ':count dydh', + 'd' => ':count dydh', + 'a_day' => ':count dydh', + + 'hour' => ':count eur', + 'h' => ':count eur', + 'a_hour' => ':count eur', + + 'minute' => ':count mynysen', + 'min' => ':count mynysen', + 'a_minute' => ':count mynysen', + + 'second' => ':count pryjwyth', + 's' => ':count pryjwyth', + 'a_second' => ':count pryjwyth', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ky.php b/libraries/Carbon/src/Carbon/Lang/ky.php new file mode 100644 index 00000000000..de18083094c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ky.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - acutexyz + * - Josh Soref + * - François B + * - Chyngyz Arystan uulu + * - Chyngyz + * - acutexyz + * - Josh Soref + * - François B + * - Chyngyz Arystan uulu + */ +return [ + 'year' => ':count жыл', + 'a_year' => '{1}бир жыл|:count жыл', + 'y' => ':count жыл', + 'month' => ':count ай', + 'a_month' => '{1}бир ай|:count ай', + 'm' => ':count ай', + 'week' => ':count апта', + 'a_week' => '{1}бир апта|:count апта', + 'w' => ':count апт.', + 'day' => ':count күн', + 'a_day' => '{1}бир күн|:count күн', + 'd' => ':count күн', + 'hour' => ':count саат', + 'a_hour' => '{1}бир саат|:count саат', + 'h' => ':count саат.', + 'minute' => ':count мүнөт', + 'a_minute' => '{1}бир мүнөт|:count мүнөт', + 'min' => ':count мүн.', + 'second' => ':count секунд', + 'a_second' => '{1}бирнече секунд|:count секунд', + 's' => ':count сек.', + 'ago' => ':time мурун', + 'from_now' => ':time ичинде', + 'diff_now' => 'азыр', + 'diff_today' => 'Бүгүн', + 'diff_today_regexp' => 'Бүгүн(?:\\s+саат)?', + 'diff_yesterday' => 'кечээ', + 'diff_yesterday_regexp' => 'Кече(?:\\s+саат)?', + 'diff_tomorrow' => 'эртең', + 'diff_tomorrow_regexp' => 'Эртең(?:\\s+саат)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бүгүн саат] LT', + 'nextDay' => '[Эртең саат] LT', + 'nextWeek' => 'dddd [саат] LT', + 'lastDay' => '[Кече саат] LT', + 'lastWeek' => '[Өткен аптанын] dddd [күнү] [саат] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + static $suffixes = [ + 0 => '-чү', + 1 => '-чи', + 2 => '-чи', + 3 => '-чү', + 4 => '-чү', + 5 => '-чи', + 6 => '-чы', + 7 => '-чи', + 8 => '-чи', + 9 => '-чу', + 10 => '-чу', + 20 => '-чы', + 30 => '-чу', + 40 => '-чы', + 50 => '-чү', + 60 => '-чы', + 70 => '-чи', + 80 => '-чи', + 90 => '-чу', + 100 => '-чү', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'months' => ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], + 'months_short' => ['янв', 'фев', 'март', 'апр', 'май', 'июнь', 'июль', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['Жекшемби', 'Дүйшөмбү', 'Шейшемби', 'Шаршемби', 'Бейшемби', 'Жума', 'Ишемби'], + 'weekdays_short' => ['Жек', 'Дүй', 'Шей', 'Шар', 'Бей', 'Жум', 'Ише'], + 'weekdays_min' => ['Жк', 'Дй', 'Шй', 'Шр', 'Бй', 'Жм', 'Иш'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => ' ', + 'meridiem' => ['таңкы', 'түштөн кийинки'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ky_KG.php b/libraries/Carbon/src/Carbon/Lang/ky_KG.php new file mode 100644 index 00000000000..c2f754a27f5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ky_KG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ky.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/lag.php b/libraries/Carbon/src/Carbon/Lang/lag.php new file mode 100644 index 00000000000..fedf2e7bbea --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lag.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['TOO', 'MUU'], + 'weekdays' => ['Jumapíiri', 'Jumatátu', 'Jumaíne', 'Jumatáano', 'Alamíisi', 'Ijumáa', 'Jumamóosi'], + 'weekdays_short' => ['Píili', 'Táatu', 'Íne', 'Táano', 'Alh', 'Ijm', 'Móosi'], + 'weekdays_min' => ['Píili', 'Táatu', 'Íne', 'Táano', 'Alh', 'Ijm', 'Móosi'], + 'months' => ['Kʉfúngatɨ', 'Kʉnaanɨ', 'Kʉkeenda', 'Kwiikumi', 'Kwiinyambála', 'Kwiidwaata', 'Kʉmʉʉnchɨ', 'Kʉvɨɨrɨ', 'Kʉsaatʉ', 'Kwiinyi', 'Kʉsaano', 'Kʉsasatʉ'], + 'months_short' => ['Fúngatɨ', 'Naanɨ', 'Keenda', 'Ikúmi', 'Inyambala', 'Idwaata', 'Mʉʉnchɨ', 'Vɨɨrɨ', 'Saatʉ', 'Inyi', 'Saano', 'Sasatʉ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/lb.php b/libraries/Carbon/src/Carbon/Lang/lb.php new file mode 100644 index 00000000000..cdc7126a4dd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lb.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - dan-nl + * - Simon Lelorrain (slelorrain) + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count Joer', + 'y' => ':countJ', + 'month' => ':count Mount|:count Méint', + 'm' => ':countMo', + 'week' => ':count Woch|:count Wochen', + 'w' => ':countWo|:countWo', + 'day' => ':count Dag|:count Deeg', + 'd' => ':countD', + 'hour' => ':count Stonn|:count Stonnen', + 'h' => ':countSto', + 'minute' => ':count Minutt|:count Minutten', + 'min' => ':countM', + 'second' => ':count Sekonn|:count Sekonnen', + 's' => ':countSek', + + 'ago' => 'virun :time', + 'from_now' => 'an :time', + 'before' => ':time virdrun', + 'after' => ':time duerno', + + 'diff_today' => 'Haut', + 'diff_yesterday' => 'Gëschter', + 'diff_yesterday_regexp' => 'Gëschter(?:\\s+um)?', + 'diff_tomorrow' => 'Muer', + 'diff_tomorrow_regexp' => 'Muer(?:\\s+um)?', + 'diff_today_regexp' => 'Haut(?:\\s+um)?', + 'formats' => [ + 'LT' => 'H:mm [Auer]', + 'LTS' => 'H:mm:ss [Auer]', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm [Auer]', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm [Auer]', + ], + + 'calendar' => [ + 'sameDay' => '[Haut um] LT', + 'nextDay' => '[Muer um] LT', + 'nextWeek' => 'dddd [um] LT', + 'lastDay' => '[Gëschter um] LT', + 'lastWeek' => function (CarbonInterface $date) { + // Different date string for 'Dënschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule + switch ($date->dayOfWeek) { + case 2: + case 4: + return '[Leschten] dddd [um] LT'; + default: + return '[Leschte] dddd [um] LT'; + } + }, + 'sameElse' => 'L', + ], + + 'months' => ['Januar', 'Februar', 'Mäerz', 'Abrëll', 'Mee', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan.', 'Febr.', 'Mrz.', 'Abr.', 'Mee', 'Jun.', 'Jul.', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.'], + 'weekdays' => ['Sonndeg', 'Méindeg', 'Dënschdeg', 'Mëttwoch', 'Donneschdeg', 'Freideg', 'Samschdeg'], + 'weekdays_short' => ['So.', 'Mé.', 'Dë.', 'Më.', 'Do.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['So', 'Mé', 'Dë', 'Më', 'Do', 'Fr', 'Sa'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' an '], + 'meridiem' => ['moies', 'mëttes'], + 'weekdays_short_standalone' => ['Son', 'Méi', 'Dën', 'Mët', 'Don', 'Fre', 'Sam'], + 'months_short_standalone' => ['Jan', 'Feb', 'Mäe', 'Abr', 'Mee', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/lb_LU.php b/libraries/Carbon/src/Carbon/Lang/lb_LU.php new file mode 100644 index 00000000000..26f94202e49 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lb_LU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lb.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/lg.php b/libraries/Carbon/src/Carbon/Lang/lg.php new file mode 100644 index 00000000000..9069da93884 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lg.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lg_UG.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/lg_UG.php b/libraries/Carbon/src/Carbon/Lang/lg_UG.php new file mode 100644 index 00000000000..e9410d957fe --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lg_UG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Akademe ya Luganda Kizito Birabwa kompyuta@kizito.uklinux.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Janwaliyo', 'Febwaliyo', 'Marisi', 'Apuli', 'Maayi', 'Juuni', 'Julaayi', 'Agusito', 'Sebuttemba', 'Okitobba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apu', 'Maa', 'Juu', 'Jul', 'Agu', 'Seb', 'Oki', 'Nov', 'Des'], + 'weekdays' => ['Sabiiti', 'Balaza', 'Lwakubiri', 'Lwakusatu', 'Lwakuna', 'Lwakutaano', 'Lwamukaaga'], + 'weekdays_short' => ['Sab', 'Bal', 'Lw2', 'Lw3', 'Lw4', 'Lw5', 'Lw6'], + 'weekdays_min' => ['Sab', 'Bal', 'Lw2', 'Lw3', 'Lw4', 'Lw5', 'Lw6'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'month' => ':count njuba', // less reliable + 'm' => ':count njuba', // less reliable + 'a_month' => ':count njuba', // less reliable + + 'year' => ':count mwaaka', + 'y' => ':count mwaaka', + 'a_year' => ':count mwaaka', + + 'week' => ':count sabbiiti', + 'w' => ':count sabbiiti', + 'a_week' => ':count sabbiiti', + + 'day' => ':count lunaku', + 'd' => ':count lunaku', + 'a_day' => ':count lunaku', + + 'hour' => 'saawa :count', + 'h' => 'saawa :count', + 'a_hour' => 'saawa :count', + + 'minute' => 'ddakiika :count', + 'min' => 'ddakiika :count', + 'a_minute' => 'ddakiika :count', + + 'second' => ':count kyʼokubiri', + 's' => ':count kyʼokubiri', + 'a_second' => ':count kyʼokubiri', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/li.php b/libraries/Carbon/src/Carbon/Lang/li.php new file mode 100644 index 00000000000..9371329c874 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/li.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/li_NL.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/li_NL.php b/libraries/Carbon/src/Carbon/Lang/li_NL.php new file mode 100644 index 00000000000..49d508de035 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/li_NL.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['jannewarie', 'fibberwarie', 'miert', 'eprèl', 'meij', 'junie', 'julie', 'augustus', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'fib', 'mie', 'epr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['zóndig', 'maondig', 'daensdig', 'goonsdig', 'dónderdig', 'vriedig', 'zaoterdig'], + 'weekdays_short' => ['zón', 'mao', 'dae', 'goo', 'dón', 'vri', 'zao'], + 'weekdays_min' => ['zón', 'mao', 'dae', 'goo', 'dón', 'vri', 'zao'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count momênt', // less reliable + 'min' => ':count momênt', // less reliable + 'a_minute' => ':count momênt', // less reliable + + 'year' => ':count jaor', + 'y' => ':count jaor', + 'a_year' => ':count jaor', + + 'month' => ':count maond', + 'm' => ':count maond', + 'a_month' => ':count maond', + + 'week' => ':count waek', + 'w' => ':count waek', + 'a_week' => ':count waek', + + 'day' => ':count daag', + 'd' => ':count daag', + 'a_day' => ':count daag', + + 'hour' => ':count oer', + 'h' => ':count oer', + 'a_hour' => ':count oer', + + 'second' => ':count Secónd', + 's' => ':count Secónd', + 'a_second' => ':count Secónd', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/lij.php b/libraries/Carbon/src/Carbon/Lang/lij.php new file mode 100644 index 00000000000..2b051413ffd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lij.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lij_IT.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/lij_IT.php b/libraries/Carbon/src/Carbon/Lang/lij_IT.php new file mode 100644 index 00000000000..6717441ff8c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lij_IT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Gastaldi alessio.gastaldi@libero.it + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['zenâ', 'fevrâ', 'marzo', 'avrî', 'mazzo', 'zûgno', 'lûggio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dixembre'], + 'months_short' => ['zen', 'fev', 'mar', 'arv', 'maz', 'zûg', 'lûg', 'ago', 'set', 'ött', 'nov', 'dix'], + 'weekdays' => ['domenega', 'lûnedì', 'martedì', 'mercUrdì', 'zêggia', 'venardì', 'sabbo'], + 'weekdays_short' => ['dom', 'lûn', 'mar', 'mer', 'zêu', 'ven', 'sab'], + 'weekdays_min' => ['dom', 'lûn', 'mar', 'mer', 'zêu', 'ven', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count etæ', // less reliable + 'y' => ':count etæ', // less reliable + 'a_year' => ':count etæ', // less reliable + + 'month' => ':count meize', + 'm' => ':count meize', + 'a_month' => ':count meize', + + 'week' => ':count settemannha', + 'w' => ':count settemannha', + 'a_week' => ':count settemannha', + + 'day' => ':count giorno', + 'd' => ':count giorno', + 'a_day' => ':count giorno', + + 'hour' => ':count reléuio', // less reliable + 'h' => ':count reléuio', // less reliable + 'a_hour' => ':count reléuio', // less reliable + + 'minute' => ':count menûo', + 'min' => ':count menûo', + 'a_minute' => ':count menûo', + + 'second' => ':count segondo', + 's' => ':count segondo', + 'a_second' => ':count segondo', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/lkt.php b/libraries/Carbon/src/Carbon/Lang/lkt.php new file mode 100644 index 00000000000..c2dcabfb66d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lkt.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + + 'month' => ':count haŋwí', // less reliable + 'm' => ':count haŋwí', // less reliable + 'a_month' => ':count haŋwí', // less reliable + + 'week' => ':count šakówiŋ', // less reliable + 'w' => ':count šakówiŋ', // less reliable + 'a_week' => ':count šakówiŋ', // less reliable + + 'hour' => ':count maza škaŋškaŋ', // less reliable + 'h' => ':count maza škaŋškaŋ', // less reliable + 'a_hour' => ':count maza škaŋškaŋ', // less reliable + + 'minute' => ':count číkʼala', // less reliable + 'min' => ':count číkʼala', // less reliable + 'a_minute' => ':count číkʼala', // less reliable + + 'year' => ':count waníyetu', + 'y' => ':count waníyetu', + 'a_year' => ':count waníyetu', + + 'day' => ':count aŋpétu', + 'd' => ':count aŋpétu', + 'a_day' => ':count aŋpétu', + + 'second' => ':count icinuŋpa', + 's' => ':count icinuŋpa', + 'a_second' => ':count icinuŋpa', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ln.php b/libraries/Carbon/src/Carbon/Lang/ln.php new file mode 100644 index 00000000000..ff8e6325393 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ln.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ubuntu René Manassé GALEKWA renemanasse@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['sánzá ya yambo', 'sánzá ya míbalé', 'sánzá ya mísáto', 'sánzá ya mínei', 'sánzá ya mítáno', 'sánzá ya motóbá', 'sánzá ya nsambo', 'sánzá ya mwambe', 'sánzá ya libwa', 'sánzá ya zómi', 'sánzá ya zómi na mɔ̌kɔ́', 'sánzá ya zómi na míbalé'], + 'months_short' => ['yan', 'fbl', 'msi', 'apl', 'mai', 'yun', 'yul', 'agt', 'stb', 'ɔtb', 'nvb', 'dsb'], + 'weekdays' => ['Lomíngo', 'Mosálá mɔ̌kɔ́', 'Misálá míbalé', 'Misálá mísáto', 'Misálá mínei', 'Misálá mítáno', 'Mpɔ́sɔ'], + 'weekdays_short' => ['m1.', 'm2.', 'm3.', 'm4.', 'm5.', 'm6.', 'm7.'], + 'weekdays_min' => ['m1.', 'm2.', 'm3.', 'm4.', 'm5.', 'm6.', 'm7.'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'mbula :count', + 'y' => 'mbula :count', + 'a_year' => 'mbula :count', + + 'month' => 'sánzá :count', + 'm' => 'sánzá :count', + 'a_month' => 'sánzá :count', + + 'week' => 'mpɔ́sɔ :count', + 'w' => 'mpɔ́sɔ :count', + 'a_week' => 'mpɔ́sɔ :count', + + 'day' => 'mokɔlɔ :count', + 'd' => 'mokɔlɔ :count', + 'a_day' => 'mokɔlɔ :count', + + 'hour' => 'ngonga :count', + 'h' => 'ngonga :count', + 'a_hour' => 'ngonga :count', + + 'minute' => 'miniti :count', + 'min' => 'miniti :count', + 'a_minute' => 'miniti :count', + + 'second' => 'segɔnde :count', + 's' => 'segɔnde :count', + 'a_second' => 'segɔnde :count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ln_AO.php b/libraries/Carbon/src/Carbon/Lang/ln_AO.php new file mode 100644 index 00000000000..73949756359 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ln_AO.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ln_CD.php b/libraries/Carbon/src/Carbon/Lang/ln_CD.php new file mode 100644 index 00000000000..ccb3740c65a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ln_CD.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ubuntu René Manassé GALEKWA renemanasse@gmail.com + */ +return require __DIR__.'/ln.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ln_CF.php b/libraries/Carbon/src/Carbon/Lang/ln_CF.php new file mode 100644 index 00000000000..73949756359 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ln_CF.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ln_CG.php b/libraries/Carbon/src/Carbon/Lang/ln_CG.php new file mode 100644 index 00000000000..73949756359 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ln_CG.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/lo.php b/libraries/Carbon/src/Carbon/Lang/lo.php new file mode 100644 index 00000000000..451f84cf620 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lo.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - ryanhart2 + */ +return [ + 'year' => ':count ປີ', + 'y' => ':count ປີ', + 'month' => ':count ເດືອນ', + 'm' => ':count ດ. ', + 'week' => ':count ອາທິດ', + 'w' => ':count ອທ. ', + 'day' => ':count ມື້', + 'd' => ':count ມື້', + 'hour' => ':count ຊົ່ວໂມງ', + 'h' => ':count ຊມ. ', + 'minute' => ':count ນາທີ', + 'min' => ':count ນທ. ', + 'second' => '{1}ບໍ່ເທົ່າໃດວິນາທີ|]1,Inf[:count ວິນາທີ', + 's' => ':count ວິ. ', + 'ago' => ':timeຜ່ານມາ', + 'from_now' => 'ອີກ :time', + 'diff_now' => 'ຕອນນີ້', + 'diff_today' => 'ມື້ນີ້ເວລາ', + 'diff_yesterday' => 'ມື້ວານນີ້ເວລາ', + 'diff_tomorrow' => 'ມື້ອື່ນເວລາ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'ວັນdddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ມື້ນີ້ເວລາ] LT', + 'nextDay' => '[ມື້ອື່ນເວລາ] LT', + 'nextWeek' => '[ວັນ]dddd[ໜ້າເວລາ] LT', + 'lastDay' => '[ມື້ວານນີ້ເວລາ] LT', + 'lastWeek' => '[ວັນ]dddd[ແລ້ວນີ້ເວລາ] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ທີ່:number', + 'meridiem' => ['ຕອນເຊົ້າ', 'ຕອນແລງ'], + 'months' => ['ມັງກອນ', 'ກຸມພາ', 'ມີນາ', 'ເມສາ', 'ພຶດສະພາ', 'ມິຖຸນາ', 'ກໍລະກົດ', 'ສິງຫາ', 'ກັນຍາ', 'ຕຸລາ', 'ພະຈິກ', 'ທັນວາ'], + 'months_short' => ['ມັງກອນ', 'ກຸມພາ', 'ມີນາ', 'ເມສາ', 'ພຶດສະພາ', 'ມິຖຸນາ', 'ກໍລະກົດ', 'ສິງຫາ', 'ກັນຍາ', 'ຕຸລາ', 'ພະຈິກ', 'ທັນວາ'], + 'weekdays' => ['ອາທິດ', 'ຈັນ', 'ອັງຄານ', 'ພຸດ', 'ພະຫັດ', 'ສຸກ', 'ເສົາ'], + 'weekdays_short' => ['ທິດ', 'ຈັນ', 'ອັງຄານ', 'ພຸດ', 'ພະຫັດ', 'ສຸກ', 'ເສົາ'], + 'weekdays_min' => ['ທ', 'ຈ', 'ອຄ', 'ພ', 'ພຫ', 'ສກ', 'ສ'], + 'list' => [', ', 'ແລະ '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/lo_LA.php b/libraries/Carbon/src/Carbon/Lang/lo_LA.php new file mode 100644 index 00000000000..1ad6f046367 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lo_LA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lo.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/lrc.php b/libraries/Carbon/src/Carbon/Lang/lrc.php new file mode 100644 index 00000000000..4e1dcb6f31b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lrc.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + + 'minute' => ':count هنر', // less reliable + 'min' => ':count هنر', // less reliable + 'a_minute' => ':count هنر', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/lrc_IQ.php b/libraries/Carbon/src/Carbon/Lang/lrc_IQ.php new file mode 100644 index 00000000000..02c1d2a2391 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lrc_IQ.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/lrc.php', [ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/lt.php b/libraries/Carbon/src/Carbon/Lang/lt.php new file mode 100644 index 00000000000..bf73db9f23a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lt.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - valdas406 + * - Justas Palumickas + * - Max Melentiev + * - Andrius Janauskas + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Laurynas Butkus + * - Sven Fuchs + * - Dominykas Tijūnaitis + * - Justinas Bolys + * - Ričardas + * - Kirill Chalkin + * - Rolandas + * - Justinas (Gamesh) + */ +return [ + 'year' => ':count metai|:count metai|:count metų', + 'y' => ':count m.', + 'month' => ':count mėnuo|:count mėnesiai|:count mėnesį', + 'm' => ':count mėn.', + 'week' => ':count savaitė|:count savaitės|:count savaitę', + 'w' => ':count sav.', + 'day' => ':count diena|:count dienos|:count dienų', + 'd' => ':count d.', + 'hour' => ':count valanda|:count valandos|:count valandų', + 'h' => ':count val.', + 'minute' => ':count minutė|:count minutės|:count minutę', + 'min' => ':count min.', + 'second' => ':count sekundė|:count sekundės|:count sekundžių', + 's' => ':count sek.', + + 'year_ago' => ':count metus|:count metus|:count metų', + 'month_ago' => ':count mėnesį|:count mėnesius|:count mėnesių', + 'week_ago' => ':count savaitę|:count savaites|:count savaičių', + 'day_ago' => ':count dieną|:count dienas|:count dienų', + 'hour_ago' => ':count valandą|:count valandas|:count valandų', + 'minute_ago' => ':count minutę|:count minutes|:count minučių', + 'second_ago' => ':count sekundę|:count sekundes|:count sekundžių', + + 'year_from_now' => ':count metų', + 'month_from_now' => ':count mėnesio|:count mėnesių|:count mėnesių', + 'week_from_now' => ':count savaitės|:count savaičių|:count savaičių', + 'day_from_now' => ':count dienos|:count dienų|:count dienų', + 'hour_from_now' => ':count valandos|:count valandų|:count valandų', + 'minute_from_now' => ':count minutės|:count minučių|:count minučių', + 'second_from_now' => ':count sekundės|:count sekundžių|:count sekundžių', + + 'year_after' => ':count metų', + 'month_after' => ':count mėnesio|:count mėnesių|:count mėnesių', + 'week_after' => ':count savaitės|:count savaičių|:count savaičių', + 'day_after' => ':count dienos|:count dienų|:count dienų', + 'hour_after' => ':count valandos|:count valandų|:count valandų', + 'minute_after' => ':count minutės|:count minučių|:count minučių', + 'second_after' => ':count sekundės|:count sekundžių|:count sekundžių', + + 'ago' => 'prieš :time', + 'from_now' => ':time nuo dabar', + 'after' => 'po :time', + 'before' => 'už :time', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'diff_now' => 'ką tik', + 'diff_today' => 'Šiandien', + 'diff_yesterday' => 'vakar', + 'diff_yesterday_regexp' => 'Vakar', + 'diff_tomorrow' => 'rytoj', + 'diff_tomorrow_regexp' => 'Rytoj', + 'diff_before_yesterday' => 'užvakar', + 'diff_after_tomorrow' => 'poryt', + + 'period_recurrences' => 'kartą|:count kartų', + 'period_interval' => 'kiekvieną :interval', + 'period_start_date' => 'nuo :date', + 'period_end_date' => 'iki :date', + + 'months' => ['sausio', 'vasario', 'kovo', 'balandžio', 'gegužės', 'birželio', 'liepos', 'rugpjūčio', 'rugsėjo', 'spalio', 'lapkričio', 'gruodžio'], + 'months_standalone' => ['sausis', 'vasaris', 'kovas', 'balandis', 'gegužė', 'birželis', 'liepa', 'rugpjūtis', 'rugsėjis', 'spalis', 'lapkritis', 'gruodis'], + 'months_regexp' => '/(L{2,4}|D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?)/', + 'months_short' => ['sau', 'vas', 'kov', 'bal', 'geg', 'bir', 'lie', 'rgp', 'rgs', 'spa', 'lap', 'gru'], + 'weekdays' => ['sekmadienį', 'pirmadienį', 'antradienį', 'trečiadienį', 'ketvirtadienį', 'penktadienį', 'šeštadienį'], + 'weekdays_standalone' => ['sekmadienis', 'pirmadienis', 'antradienis', 'trečiadienis', 'ketvirtadienis', 'penktadienis', 'šeštadienis'], + 'weekdays_short' => ['sek', 'pir', 'ant', 'tre', 'ket', 'pen', 'šeš'], + 'weekdays_min' => ['se', 'pi', 'an', 'tr', 'ke', 'pe', 'še'], + 'list' => [', ', ' ir '], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Šiandien] LT', + 'nextDay' => '[Rytoj] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[Vakar] LT', + 'lastWeek' => '[Paskutinį] dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + switch ($number) { + case 0: + return '0-is'; + case 3: + return '3-ias'; + default: + return "$number-as"; + } + }, + 'meridiem' => ['priešpiet', 'popiet'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/lt_LT.php b/libraries/Carbon/src/Carbon/Lang/lt_LT.php new file mode 100644 index 00000000000..61091a63f73 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lt_LT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/lu.php b/libraries/Carbon/src/Carbon/Lang/lu.php new file mode 100644 index 00000000000..4d98d570126 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lu.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Dinda', 'Dilolo'], + 'weekdays' => ['Lumingu', 'Nkodya', 'Ndàayà', 'Ndangù', 'Njòwa', 'Ngòvya', 'Lubingu'], + 'weekdays_short' => ['Lum', 'Nko', 'Ndy', 'Ndg', 'Njw', 'Ngv', 'Lub'], + 'weekdays_min' => ['Lum', 'Nko', 'Ndy', 'Ndg', 'Njw', 'Ngv', 'Lub'], + 'months' => ['Ciongo', 'Lùishi', 'Lusòlo', 'Mùuyà', 'Lumùngùlù', 'Lufuimi', 'Kabàlàshìpù', 'Lùshìkà', 'Lutongolo', 'Lungùdi', 'Kaswèkèsè', 'Ciswà'], + 'months_short' => ['Cio', 'Lui', 'Lus', 'Muu', 'Lum', 'Luf', 'Kab', 'Lush', 'Lut', 'Lun', 'Kas', 'Cis'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/luo.php b/libraries/Carbon/src/Carbon/Lang/luo.php new file mode 100644 index 00000000000..03494206586 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/luo.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['OD', 'OT'], + 'weekdays' => ['Jumapil', 'Wuok Tich', 'Tich Ariyo', 'Tich Adek', 'Tich Ang’wen', 'Tich Abich', 'Ngeso'], + 'weekdays_short' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], + 'weekdays_min' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], + 'months' => ['Dwe mar Achiel', 'Dwe mar Ariyo', 'Dwe mar Adek', 'Dwe mar Ang’wen', 'Dwe mar Abich', 'Dwe mar Auchiel', 'Dwe mar Abiriyo', 'Dwe mar Aboro', 'Dwe mar Ochiko', 'Dwe mar Apar', 'Dwe mar gi achiel', 'Dwe mar Apar gi ariyo'], + 'months_short' => ['DAC', 'DAR', 'DAD', 'DAN', 'DAH', 'DAU', 'DAO', 'DAB', 'DOC', 'DAP', 'DGI', 'DAG'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => 'higni :count', + 'y' => 'higni :count', + 'a_year' => ':higni :count', + + 'month' => 'dweche :count', + 'm' => 'dweche :count', + 'a_month' => 'dweche :count', + + 'week' => 'jumbe :count', + 'w' => 'jumbe :count', + 'a_week' => 'jumbe :count', + + 'day' => 'ndalo :count', + 'd' => 'ndalo :count', + 'a_day' => 'ndalo :count', + + 'hour' => 'seche :count', + 'h' => 'seche :count', + 'a_hour' => 'seche :count', + + 'minute' => 'dakika :count', + 'min' => 'dakika :count', + 'a_minute' => 'dakika :count', + + 'second' => 'nus dakika :count', + 's' => 'nus dakika :count', + 'a_second' => 'nus dakika :count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/luy.php b/libraries/Carbon/src/Carbon/Lang/luy.php new file mode 100644 index 00000000000..3f323b6c1cb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/luy.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Jumapiri', 'Jumatatu', 'Jumanne', 'Jumatano', 'Murwa wa Kanne', 'Murwa wa Katano', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count liliino', // less reliable + 'y' => ':count liliino', // less reliable + 'a_year' => ':count liliino', // less reliable + + 'month' => ':count kumwesi', // less reliable + 'm' => ':count kumwesi', // less reliable + 'a_month' => ':count kumwesi', // less reliable + + 'week' => ':count olutambi', // less reliable + 'w' => ':count olutambi', // less reliable + 'a_week' => ':count olutambi', // less reliable + + 'day' => ':count luno', // less reliable + 'd' => ':count luno', // less reliable + 'a_day' => ':count luno', // less reliable + + 'hour' => ':count ekengele', // less reliable + 'h' => ':count ekengele', // less reliable + 'a_hour' => ':count ekengele', // less reliable + + 'minute' => ':count omundu', // less reliable + 'min' => ':count omundu', // less reliable + 'a_minute' => ':count omundu', // less reliable + + 'second' => ':count liliino', // less reliable + 's' => ':count liliino', // less reliable + 'a_second' => ':count liliino', // less reliable + */ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/lv.php b/libraries/Carbon/src/Carbon/Lang/lv.php new file mode 100644 index 00000000000..d2bc04516d6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lv.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +/** + * This file is part of the EDD\Vendor\Carbon package. + * + * (c) Brian Nesbitt + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - pirminis + * - Tsutomu Kuroda + * - tjku + * - Andris Zāģeris + * - Max Melentiev + * - Edgars Beigarts + * - Juanito Fatas + * - Vitauts Stočka + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Kaspars Bankovskis + * - Nicolás Hock Isaza + * - Viesturs Kavacs (Kavacky) + * - zakse + * - Janis Eglitis (janiseglitis) + * - Guntars + * - Juris Sudmalis + */ +$daysOfWeek = ['svētdiena', 'pirmdiena', 'otrdiena', 'trešdiena', 'ceturtdiena', 'piektdiena', 'sestdiena']; +$daysOfWeekLocativum = ['svētdien', 'pirmdien', 'otrdien', 'trešdien', 'ceturtdien', 'piektdien', 'sestdien']; + +$transformDiff = function ($input) { + return strtr($input, [ + // Nominative => "pirms/pēc" Dative + 'gads' => 'gada', + 'gadi' => 'gadiem', + 'gadu' => 'gadiem', + 'mēnesis' => 'mēneša', + 'mēneši' => 'mēnešiem', + 'mēnešu' => 'mēnešiem', + 'nedēļa' => 'nedēļas', + 'nedēļas' => 'nedēļām', + 'nedēļu' => 'nedēļām', + 'diena' => 'dienas', + 'dienas' => 'dienām', + 'dienu' => 'dienām', + 'stunda' => 'stundas', + 'stundas' => 'stundām', + 'stundu' => 'stundām', + 'minūte' => 'minūtes', + 'minūtes' => 'minūtēm', + 'minūšu' => 'minūtēm', + 'sekunde' => 'sekundes', + 'sekundes' => 'sekundēm', + 'sekunžu' => 'sekundēm', + ]); +}; + +return [ + 'ago' => function ($time) use ($transformDiff) { + return 'pirms '.$transformDiff($time); + }, + 'from_now' => function ($time) use ($transformDiff) { + return 'pēc '.$transformDiff($time); + }, + + 'year' => '0 gadu|:count gads|:count gadi', + 'y' => ':count g.', + 'a_year' => '{1}gads|0 gadu|:count gads|:count gadi', + 'month' => '0 mēnešu|:count mēnesis|:count mēneši', + 'm' => ':count mēn.', + 'a_month' => '{1}mēnesis|0 mēnešu|:count mēnesis|:count mēneši', + 'week' => '0 nedēļu|:count nedēļa|:count nedēļas', + 'w' => ':count ned.', + 'a_week' => '{1}nedēļa|0 nedēļu|:count nedēļa|:count nedēļas', + 'day' => '0 dienu|:count diena|:count dienas', + 'd' => ':count d.', + 'a_day' => '{1}diena|0 dienu|:count diena|:count dienas', + 'hour' => '0 stundu|:count stunda|:count stundas', + 'h' => ':count st.', + 'a_hour' => '{1}stunda|0 stundu|:count stunda|:count stundas', + 'minute' => '0 minūšu|:count minūte|:count minūtes', + 'min' => ':count min.', + 'a_minute' => '{1}minūte|0 minūšu|:count minūte|:count minūtes', + 'second' => '0 sekunžu|:count sekunde|:count sekundes', + 's' => ':count sek.', + 'a_second' => '{1}sekunde|0 sekunžu|:count sekunde|:count sekundes', + + 'after' => ':time vēlāk', + 'year_after' => '0 gadus|:count gadu|:count gadus', + 'a_year_after' => '{1}gadu|0 gadus|:count gadu|:count gadus', + 'month_after' => '0 mēnešus|:count mēnesi|:count mēnešus', + 'a_month_after' => '{1}mēnesi|0 mēnešus|:count mēnesi|:count mēnešus', + 'week_after' => '0 nedēļas|:count nedēļu|:count nedēļas', + 'a_week_after' => '{1}nedēļu|0 nedēļas|:count nedēļu|:count nedēļas', + 'day_after' => '0 dienas|:count dienu|:count dienas', + 'a_day_after' => '{1}dienu|0 dienas|:count dienu|:count dienas', + 'hour_after' => '0 stundas|:count stundu|:count stundas', + 'a_hour_after' => '{1}stundu|0 stundas|:count stundu|:count stundas', + 'minute_after' => '0 minūtes|:count minūti|:count minūtes', + 'a_minute_after' => '{1}minūti|0 minūtes|:count minūti|:count minūtes', + 'second_after' => '0 sekundes|:count sekundi|:count sekundes', + 'a_second_after' => '{1}sekundi|0 sekundes|:count sekundi|:count sekundes', + + 'before' => ':time agrāk', + 'year_before' => '0 gadus|:count gadu|:count gadus', + 'a_year_before' => '{1}gadu|0 gadus|:count gadu|:count gadus', + 'month_before' => '0 mēnešus|:count mēnesi|:count mēnešus', + 'a_month_before' => '{1}mēnesi|0 mēnešus|:count mēnesi|:count mēnešus', + 'week_before' => '0 nedēļas|:count nedēļu|:count nedēļas', + 'a_week_before' => '{1}nedēļu|0 nedēļas|:count nedēļu|:count nedēļas', + 'day_before' => '0 dienas|:count dienu|:count dienas', + 'a_day_before' => '{1}dienu|0 dienas|:count dienu|:count dienas', + 'hour_before' => '0 stundas|:count stundu|:count stundas', + 'a_hour_before' => '{1}stundu|0 stundas|:count stundu|:count stundas', + 'minute_before' => '0 minūtes|:count minūti|:count minūtes', + 'a_minute_before' => '{1}minūti|0 minūtes|:count minūti|:count minūtes', + 'second_before' => '0 sekundes|:count sekundi|:count sekundes', + 'a_second_before' => '{1}sekundi|0 sekundes|:count sekundi|:count sekundes', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' un '], + + 'diff_now' => 'tagad', + 'diff_today' => 'šodien', + 'diff_yesterday' => 'vakar', + 'diff_before_yesterday' => 'aizvakar', + 'diff_tomorrow' => 'rīt', + 'diff_after_tomorrow' => 'parīt', + + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY.', + 'LL' => 'YYYY. [gada] D. MMMM', + 'LLL' => 'DD.MM.YYYY., HH:mm', + 'LLLL' => 'YYYY. [gada] D. MMMM, HH:mm', + ], + + 'calendar' => [ + 'sameDay' => '[šodien] [plkst.] LT', + 'nextDay' => '[rīt] [plkst.] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) use ($daysOfWeekLocativum) { + if ($current->week !== $other->week) { + return '[nākošo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + } + + return '['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + }, + 'lastDay' => '[vakar] [plkst.] LT', + 'lastWeek' => function (CarbonInterface $current) use ($daysOfWeekLocativum) { + return '[pagājušo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + }, + 'sameElse' => 'L', + ], + + 'weekdays' => $daysOfWeek, + 'weekdays_short' => ['Sv.', 'P.', 'O.', 'T.', 'C.', 'Pk.', 'S.'], + 'weekdays_min' => ['Sv.', 'P.', 'O.', 'T.', 'C.', 'Pk.', 'S.'], + 'months' => ['janvāris', 'februāris', 'marts', 'aprīlis', 'maijs', 'jūnijs', 'jūlijs', 'augusts', 'septembris', 'oktobris', 'novembris', 'decembris'], + 'months_standalone' => ['janvārī', 'februārī', 'martā', 'aprīlī', 'maijā', 'jūnijā', 'jūlijā', 'augustā', 'septembrī', 'oktobrī', 'novembrī', 'decembrī'], + 'months_short' => ['janv.', 'febr.', 'martā', 'apr.', 'maijā', 'jūn.', 'jūl.', 'aug.', 'sept.', 'okt.', 'nov.', 'dec.'], + 'meridiem' => ['priekšpusdiena', 'pēcpusdiena'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/lv_LV.php b/libraries/Carbon/src/Carbon/Lang/lv_LV.php new file mode 100644 index 00000000000..ae7d9e9a7de --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lv_LV.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lv.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/lzh.php b/libraries/Carbon/src/Carbon/Lang/lzh.php new file mode 100644 index 00000000000..a68ffdc2e41 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lzh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lzh_TW.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/lzh_TW.php b/libraries/Carbon/src/Carbon/Lang/lzh_TW.php new file mode 100644 index 00000000000..27eeebb8cf4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/lzh_TW.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY[年]MMMMOD[日]', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 一 ', ' 二 ', ' 三 ', ' 四 ', ' 五 ', ' 六 ', ' 七 ', ' 八 ', ' 九 ', ' 十 ', '十一', '十二'], + 'weekdays' => ['週日', '週一', '週二', '週三', '週四', '週五', '週六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '廿', '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '卅', '卅一'], + 'meridiem' => ['朝', '暮'], + + 'year' => ':count 夏', // less reliable + 'y' => ':count 夏', // less reliable + 'a_year' => ':count 夏', // less reliable + + 'month' => ':count 月', // less reliable + 'm' => ':count 月', // less reliable + 'a_month' => ':count 月', // less reliable + + 'hour' => ':count 氧', // less reliable + 'h' => ':count 氧', // less reliable + 'a_hour' => ':count 氧', // less reliable + + 'minute' => ':count 點', // less reliable + 'min' => ':count 點', // less reliable + 'a_minute' => ':count 點', // less reliable + + 'second' => ':count 楚', // less reliable + 's' => ':count 楚', // less reliable + 'a_second' => ':count 楚', // less reliable + + 'week' => ':count 星期', + 'w' => ':count 星期', + 'a_week' => ':count 星期', + + 'day' => ':count 日(曆法)', + 'd' => ':count 日(曆法)', + 'a_day' => ':count 日(曆法)', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mag.php b/libraries/Carbon/src/Carbon/Lang/mag.php new file mode 100644 index 00000000000..6f5a9b0e5c3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mag.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mag_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mag_IN.php b/libraries/Carbon/src/Carbon/Lang/mag_IN.php new file mode 100644 index 00000000000..b05f5156306 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mag_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'weekdays_short' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'weekdays_min' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mai.php b/libraries/Carbon/src/Carbon/Lang/mai.php new file mode 100644 index 00000000000..52d403159ee --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mai.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mai_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mai_IN.php b/libraries/Carbon/src/Carbon/Lang/mai_IN.php new file mode 100644 index 00000000000..9a670c425eb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mai_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Maithili Computing Research Center, Pune, India rajeshkajha@yahoo.com,akhilesh.k@samusng.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['बैसाख', 'जेठ', 'अषाढ़', 'सावोन', 'भादो', 'आसिन', 'कातिक', 'अगहन', 'पूस', 'माघ', 'फागुन', 'चैति'], + 'months_short' => ['बैसाख', 'जेठ', 'अषाढ़', 'सावोन', 'भादो', 'आसिन', 'कातिक', 'अगहन', 'पूस', 'माघ', 'फागुन', 'चैति'], + 'weekdays' => ['रविदिन', 'सोमदिन', 'मंगलदिन', 'बुधदिन', 'बृहस्पतीदिन', 'शुक्रदिन', 'शनीदिन'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पती', 'शुक्र', 'शनी'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पती', 'शुक्र', 'शनी'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'year' => ':count ऋतु', // less reliable + 'y' => ':count ऋतु', // less reliable + 'a_year' => ':count ऋतु', // less reliable + + 'month' => ':count महिना', + 'm' => ':count महिना', + 'a_month' => ':count महिना', + + 'week' => ':count श्रेणी:क्यालेन्डर', // less reliable + 'w' => ':count श्रेणी:क्यालेन्डर', // less reliable + 'a_week' => ':count श्रेणी:क्यालेन्डर', // less reliable + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', + + 'hour' => ':count घण्टा', + 'h' => ':count घण्टा', + 'a_hour' => ':count घण्टा', + + 'minute' => ':count समय', // less reliable + 'min' => ':count समय', // less reliable + 'a_minute' => ':count समय', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mas.php b/libraries/Carbon/src/Carbon/Lang/mas.php new file mode 100644 index 00000000000..77d54a2fa24 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mas.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ɛnkakɛnyá', 'Ɛndámâ'], + 'weekdays' => ['Jumapílí', 'Jumatátu', 'Jumane', 'Jumatánɔ', 'Alaámisi', 'Jumáa', 'Jumamósi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Oladalʉ́', 'Arát', 'Ɔɛnɨ́ɔɨŋɔk', 'Olodoyíóríê inkókúâ', 'Oloilépūnyīē inkókúâ', 'Kújúɔrɔk', 'Mórusásin', 'Ɔlɔ́ɨ́bɔ́rárɛ', 'Kúshîn', 'Olgísan', 'Pʉshʉ́ka', 'Ntʉ́ŋʉ́s'], + 'months_short' => ['Dal', 'Ará', 'Ɔɛn', 'Doy', 'Lép', 'Rok', 'Sás', 'Bɔ́r', 'Kús', 'Gís', 'Shʉ́', 'Ntʉ́'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count olameyu', // less reliable + 'y' => ':count olameyu', // less reliable + 'a_year' => ':count olameyu', // less reliable + + 'week' => ':count engolongeare orwiki', // less reliable + 'w' => ':count engolongeare orwiki', // less reliable + 'a_week' => ':count engolongeare orwiki', // less reliable + + 'hour' => ':count esahabu', // less reliable + 'h' => ':count esahabu', // less reliable + 'a_hour' => ':count esahabu', // less reliable + + 'second' => ':count are', // less reliable + 's' => ':count are', // less reliable + 'a_second' => ':count are', // less reliable + + 'month' => ':count olapa', + 'm' => ':count olapa', + 'a_month' => ':count olapa', + + 'day' => ':count enkolongʼ', + 'd' => ':count enkolongʼ', + 'a_day' => ':count enkolongʼ', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mas_TZ.php b/libraries/Carbon/src/Carbon/Lang/mas_TZ.php new file mode 100644 index 00000000000..b2a98eade15 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mas_TZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/mas.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mer.php b/libraries/Carbon/src/Carbon/Lang/mer.php new file mode 100644 index 00000000000..35b936b5391 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mer.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['RŨ', 'ŨG'], + 'weekdays' => ['Kiumia', 'Muramuko', 'Wairi', 'Wethatu', 'Wena', 'Wetano', 'Jumamosi'], + 'weekdays_short' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], + 'weekdays_min' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], + 'months' => ['Januarĩ', 'Feburuarĩ', 'Machi', 'Ĩpurũ', 'Mĩĩ', 'Njuni', 'Njuraĩ', 'Agasti', 'Septemba', 'Oktũba', 'Novemba', 'Dicemba'], + 'months_short' => ['JAN', 'FEB', 'MAC', 'ĨPU', 'MĨĨ', 'NJU', 'NJR', 'AGA', 'SPT', 'OKT', 'NOV', 'DEC'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count murume', // less reliable + 'y' => ':count murume', // less reliable + 'a_year' => ':count murume', // less reliable + + 'month' => ':count muchaara', // less reliable + 'm' => ':count muchaara', // less reliable + 'a_month' => ':count muchaara', // less reliable + + 'minute' => ':count monto', // less reliable + 'min' => ':count monto', // less reliable + 'a_minute' => ':count monto', // less reliable + + 'second' => ':count gikeno', // less reliable + 's' => ':count gikeno', // less reliable + 'a_second' => ':count gikeno', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mfe.php b/libraries/Carbon/src/Carbon/Lang/mfe.php new file mode 100644 index 00000000000..1ae1603c9d7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mfe.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mfe_MU.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mfe_MU.php b/libraries/Carbon/src/Carbon/Lang/mfe_MU.php new file mode 100644 index 00000000000..47e004f62fb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mfe_MU.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['zanvie', 'fevriye', 'mars', 'avril', 'me', 'zin', 'zilye', 'out', 'septam', 'oktob', 'novam', 'desam'], + 'months_short' => ['zan', 'fev', 'mar', 'avr', 'me', 'zin', 'zil', 'out', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['dimans', 'lindi', 'mardi', 'merkredi', 'zedi', 'vandredi', 'samdi'], + 'weekdays_short' => ['dim', 'lin', 'mar', 'mer', 'ze', 'van', 'sam'], + 'weekdays_min' => ['dim', 'lin', 'mar', 'mer', 'ze', 'van', 'sam'], + + 'year' => ':count banané', + 'y' => ':count banané', + 'a_year' => ':count banané', + + 'month' => ':count mwa', + 'm' => ':count mwa', + 'a_month' => ':count mwa', + + 'week' => ':count sémenn', + 'w' => ':count sémenn', + 'a_week' => ':count sémenn', + + 'day' => ':count zour', + 'd' => ':count zour', + 'a_day' => ':count zour', + + 'hour' => ':count -er-tan', + 'h' => ':count -er-tan', + 'a_hour' => ':count -er-tan', + + 'minute' => ':count minitt', + 'min' => ':count minitt', + 'a_minute' => ':count minitt', + + 'second' => ':count déziém', + 's' => ':count déziém', + 'a_second' => ':count déziém', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mg.php b/libraries/Carbon/src/Carbon/Lang/mg.php new file mode 100644 index 00000000000..1be0766d320 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mg.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mg_MG.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mg_MG.php b/libraries/Carbon/src/Carbon/Lang/mg_MG.php new file mode 100644 index 00000000000..e26d8019f31 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mg_MG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian Project modified by GNU//Linux Malagasy Rado Ramarotafika,Do-Risika RAFIEFERANTSIARONJY rado@linuxmg.org,dourix@free.fr + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Janoary', 'Febroary', 'Martsa', 'Aprily', 'Mey', 'Jona', 'Jolay', 'Aogositra', 'Septambra', 'Oktobra', 'Novambra', 'Desambra'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mey', 'Jon', 'Jol', 'Aog', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['alahady', 'alatsinainy', 'talata', 'alarobia', 'alakamisy', 'zoma', 'sabotsy'], + 'weekdays_short' => ['lhd', 'lts', 'tlt', 'lrb', 'lkm', 'zom', 'sab'], + 'weekdays_min' => ['lhd', 'lts', 'tlt', 'lrb', 'lkm', 'zom', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count minitra', // less reliable + 'min' => ':count minitra', // less reliable + 'a_minute' => ':count minitra', // less reliable + + 'year' => ':count taona', + 'y' => ':count taona', + 'a_year' => ':count taona', + + 'month' => ':count volana', + 'm' => ':count volana', + 'a_month' => ':count volana', + + 'week' => ':count herinandro', + 'w' => ':count herinandro', + 'a_week' => ':count herinandro', + + 'day' => ':count andro', + 'd' => ':count andro', + 'a_day' => ':count andro', + + 'hour' => ':count ora', + 'h' => ':count ora', + 'a_hour' => ':count ora', + + 'second' => ':count segondra', + 's' => ':count segondra', + 'a_second' => ':count segondra', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mgh.php b/libraries/Carbon/src/Carbon/Lang/mgh.php new file mode 100644 index 00000000000..2b56b6f8e67 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mgh.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['wichishu', 'mchochil’l'], + 'weekdays' => ['Sabato', 'Jumatatu', 'Jumanne', 'Jumatano', 'Arahamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], + 'weekdays_min' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], + 'months' => ['Mweri wo kwanza', 'Mweri wo unayeli', 'Mweri wo uneraru', 'Mweri wo unecheshe', 'Mweri wo unethanu', 'Mweri wo thanu na mocha', 'Mweri wo saba', 'Mweri wo nane', 'Mweri wo tisa', 'Mweri wo kumi', 'Mweri wo kumi na moja', 'Mweri wo kumi na yel’li'], + 'months_short' => ['Kwa', 'Una', 'Rar', 'Che', 'Tha', 'Moc', 'Sab', 'Nan', 'Tis', 'Kum', 'Moj', 'Yel'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mgo.php b/libraries/Carbon/src/Carbon/Lang/mgo.php new file mode 100644 index 00000000000..adb1dd5fa92 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mgo.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Aneg 1', 'Aneg 2', 'Aneg 3', 'Aneg 4', 'Aneg 5', 'Aneg 6', 'Aneg 7'], + 'weekdays_short' => ['Aneg 1', 'Aneg 2', 'Aneg 3', 'Aneg 4', 'Aneg 5', 'Aneg 6', 'Aneg 7'], + 'weekdays_min' => ['1', '2', '3', '4', '5', '6', '7'], + 'months' => ['iməg mbegtug', 'imeg àbùbì', 'imeg mbəŋchubi', 'iməg ngwə̀t', 'iməg fog', 'iməg ichiibɔd', 'iməg àdùmbə̀ŋ', 'iməg ichika', 'iməg kud', 'iməg tèsiʼe', 'iməg zò', 'iməg krizmed'], + 'months_short' => ['mbegtug', 'imeg àbùbì', 'imeg mbəŋchubi', 'iməg ngwə̀t', 'iməg fog', 'iməg ichiibɔd', 'iməg àdùmbə̀ŋ', 'iməg ichika', 'iməg kud', 'iməg tèsiʼe', 'iməg zò', 'iməg krizmed'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mhr.php b/libraries/Carbon/src/Carbon/Lang/mhr.php new file mode 100644 index 00000000000..8cc8e31b20f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mhr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mhr_RU.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mhr_RU.php b/libraries/Carbon/src/Carbon/Lang/mhr_RU.php new file mode 100644 index 00000000000..9c758516d2e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mhr_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - PeshSajSoft Ltd. Vyacheslav Kileev slavakileev@yandex.ru + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['Шорыкйол', 'Пургыж', 'Ӱярня', 'Вӱдшор', 'Ага', 'Пеледыш', 'Сӱрем', 'Сорла', 'Идым', 'Шыжа', 'Кылме', 'Теле'], + 'months_short' => ['Шрк', 'Пгж', 'Ӱрн', 'Вшр', 'Ага', 'Пдш', 'Срм', 'Срл', 'Идм', 'Шыж', 'Клм', 'Тел'], + 'weekdays' => ['Рушарня', 'Шочмо', 'Кушкыжмо', 'Вӱргече', 'Изарня', 'Кугарня', 'Шуматкече'], + 'weekdays_short' => ['Ршр', 'Шчм', 'Кжм', 'Вгч', 'Изр', 'Кгр', 'Шмт'], + 'weekdays_min' => ['Ршр', 'Шчм', 'Кжм', 'Вгч', 'Изр', 'Кгр', 'Шмт'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count идалык', + 'y' => ':count идалык', + 'a_year' => ':count идалык', + + 'month' => ':count Тылзе', + 'm' => ':count Тылзе', + 'a_month' => ':count Тылзе', + + 'week' => ':count арня', + 'w' => ':count арня', + 'a_week' => ':count арня', + + 'day' => ':count кече', + 'd' => ':count кече', + 'a_day' => ':count кече', + + 'hour' => ':count час', + 'h' => ':count час', + 'a_hour' => ':count час', + + 'minute' => ':count минут', + 'min' => ':count минут', + 'a_minute' => ':count минут', + + 'second' => ':count кокымшан', + 's' => ':count кокымшан', + 'a_second' => ':count кокымшан', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mi.php b/libraries/Carbon/src/Carbon/Lang/mi.php new file mode 100644 index 00000000000..2bfe94e0b65 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mi.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - John Corrigan + * - François B + */ +return [ + 'year' => ':count tau', + 'a_year' => '{1}he tau|:count tau', + 'month' => ':count marama', + 'a_month' => '{1}he marama|:count marama', + 'week' => ':count wiki', + 'a_week' => '{1}he wiki|:count wiki', + 'day' => ':count ra', + 'a_day' => '{1}he ra|:count ra', + 'hour' => ':count haora', + 'a_hour' => '{1}te haora|:count haora', + 'minute' => ':count meneti', + 'a_minute' => '{1}he meneti|:count meneti', + 'second' => ':count hēkona', + 'a_second' => '{1}te hēkona ruarua|:count hēkona', + 'ago' => ':time i mua', + 'from_now' => 'i roto i :time', + 'diff_yesterday' => 'inanahi', + 'diff_yesterday_regexp' => 'inanahi(?:\\s+i)?', + 'diff_today' => 'i teie', + 'diff_today_regexp' => 'i teie(?:\\s+mahana,)?(?:\\s+i)?', + 'diff_tomorrow' => 'apopo', + 'diff_tomorrow_regexp' => 'apopo(?:\\s+i)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [i] HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY [i] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i teie mahana, i] LT', + 'nextDay' => '[apopo i] LT', + 'nextWeek' => 'dddd [i] LT', + 'lastDay' => '[inanahi i] LT', + 'lastWeek' => 'dddd [whakamutunga i] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Kohi-tāte', 'Hui-tanguru', 'Poutū-te-rangi', 'Paenga-whāwhā', 'Haratua', 'Pipiri', 'Hōngoingoi', 'Here-turi-kōkā', 'Mahuru', 'Whiringa-ā-nuku', 'Whiringa-ā-rangi', 'Hakihea'], + 'months_short' => ['Kohi', 'Hui', 'Pou', 'Pae', 'Hara', 'Pipi', 'Hōngoi', 'Here', 'Mahu', 'Whi-nu', 'Whi-ra', 'Haki'], + 'weekdays' => ['Rātapu', 'Mane', 'Tūrei', 'Wenerei', 'Tāite', 'Paraire', 'Hātarei'], + 'weekdays_short' => ['Ta', 'Ma', 'Tū', 'We', 'Tāi', 'Pa', 'Hā'], + 'weekdays_min' => ['Ta', 'Ma', 'Tū', 'We', 'Tāi', 'Pa', 'Hā'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' me te '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/mi_NZ.php b/libraries/Carbon/src/Carbon/Lang/mi_NZ.php new file mode 100644 index 00000000000..2d760223e2c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mi_NZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mi.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/miq.php b/libraries/Carbon/src/Carbon/Lang/miq.php new file mode 100644 index 00000000000..8b626dd642c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/miq.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/miq_NI.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/miq_NI.php b/libraries/Carbon/src/Carbon/Lang/miq_NI.php new file mode 100644 index 00000000000..82c47ba2d51 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/miq_NI.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['siakwa kati', 'kuswa kati', 'kakamuk kati', 'lî wainhka kati', 'lih mairin kati', 'lî kati', 'pastara kati', 'sikla kati', 'wîs kati', 'waupasa kati', 'yahbra kati', 'trisu kati'], + 'months_short' => ['siakwa kati', 'kuswa kati', 'kakamuk kati', 'lî wainhka kati', 'lih mairin kati', 'lî kati', 'pastara kati', 'sikla kati', 'wîs kati', 'waupasa kati', 'yahbra kati', 'trisu kati'], + 'weekdays' => ['sandi', 'mundi', 'tiusdi', 'wensde', 'tausde', 'praidi', 'satadi'], + 'weekdays_short' => ['san', 'mun', 'tius', 'wens', 'taus', 'prai', 'sat'], + 'weekdays_min' => ['san', 'mun', 'tius', 'wens', 'taus', 'prai', 'sat'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 7, + 'meridiem' => ['VM', 'NM'], + + 'month' => ':count kati', // less reliable + 'm' => ':count kati', // less reliable + 'a_month' => ':count kati', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mjw.php b/libraries/Carbon/src/Carbon/Lang/mjw.php new file mode 100644 index 00000000000..52344193d31 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mjw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mjw_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mjw_IN.php b/libraries/Carbon/src/Carbon/Lang/mjw_IN.php new file mode 100644 index 00000000000..8ea76f5ad3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mjw_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Jor Teron bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['Arkoi', 'Thangthang', 'There', 'Jangmi', 'Aru', 'Vosik', 'Jakhong', 'Paipai', 'Chiti', 'Phere', 'Phaikuni', 'Matijong'], + 'months_short' => ['Ark', 'Thang', 'The', 'Jang', 'Aru', 'Vos', 'Jak', 'Pai', 'Chi', 'Phe', 'Phai', 'Mati'], + 'weekdays' => ['Bhomkuru', 'Urmi', 'Durmi', 'Thelang', 'Theman', 'Bhomta', 'Bhomti'], + 'weekdays_short' => ['Bhom', 'Ur', 'Dur', 'Tkel', 'Tkem', 'Bhta', 'Bhti'], + 'weekdays_min' => ['Bhom', 'Ur', 'Dur', 'Tkel', 'Tkem', 'Bhta', 'Bhti'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mk.php b/libraries/Carbon/src/Carbon/Lang/mk.php new file mode 100644 index 00000000000..a66f3163a0e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mk.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sashko Todorov + * - Josh Soref + * - François B + * - Serhan Apaydın + * - Borislav Mickov + * - JD Isaacks + * - Tomi Atanasoski + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count година|:count години', + 'a_year' => 'година|:count години', + 'y' => ':count год.', + 'month' => ':count месец|:count месеци', + 'a_month' => 'месец|:count месеци', + 'm' => ':count месец|:count месеци', + 'week' => ':count седмица|:count седмици', + 'a_week' => 'седмица|:count седмици', + 'w' => ':count седмица|:count седмици', + 'day' => ':count ден|:count дена', + 'a_day' => 'ден|:count дена', + 'd' => ':count ден|:count дена', + 'hour' => ':count час|:count часа', + 'a_hour' => 'час|:count часа', + 'h' => ':count час|:count часа', + 'minute' => ':count минута|:count минути', + 'a_minute' => 'минута|:count минути', + 'min' => ':count мин.', + 'second' => ':count секунда|:count секунди', + 'a_second' => 'неколку секунди|:count секунди', + 's' => ':count сек.', + 'ago' => 'пред :time', + 'from_now' => 'после :time', + 'after' => 'по :time', + 'before' => 'пред :time', + 'diff_now' => 'сега', + 'diff_today' => 'Денес', + 'diff_today_regexp' => 'Денес(?:\\s+во)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера(?:\\s+во)?', + 'diff_tomorrow' => 'утре', + 'diff_tomorrow_regexp' => 'Утре(?:\\s+во)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Денес во] LT', + 'nextDay' => '[Утре во] LT', + 'nextWeek' => '[Во] dddd [во] LT', + 'lastDay' => '[Вчера во] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 3: + case 6: + return '[Изминатата] dddd [во] LT'; + default: + return '[Изминатиот] dddd [во] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + $last2Digits = $number % 100; + if ($number === 0) { + return $number.'-ев'; + } + if ($last2Digits === 0) { + return $number.'-ен'; + } + if ($last2Digits > 10 && $last2Digits < 20) { + return $number.'-ти'; + } + if ($lastDigit === 1) { + return $number.'-ви'; + } + if ($lastDigit === 2) { + return $number.'-ри'; + } + if ($lastDigit === 7 || $lastDigit === 8) { + return $number.'-ми'; + } + + return $number.'-ти'; + }, + 'months' => ['јануари', 'февруари', 'март', 'април', 'мај', 'јуни', 'јули', 'август', 'септември', 'октомври', 'ноември', 'декември'], + 'months_short' => ['јан', 'фев', 'мар', 'апр', 'мај', 'јун', 'јул', 'авг', 'сеп', 'окт', 'ное', 'дек'], + 'weekdays' => ['недела', 'понеделник', 'вторник', 'среда', 'четврток', 'петок', 'сабота'], + 'weekdays_short' => ['нед', 'пон', 'вто', 'сре', 'чет', 'пет', 'саб'], + 'weekdays_min' => ['нe', 'пo', 'вт', 'ср', 'че', 'пе', 'сa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/mk_MK.php b/libraries/Carbon/src/Carbon/Lang/mk_MK.php new file mode 100644 index 00000000000..74454fe23c4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mk_MK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mk.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ml.php b/libraries/Carbon/src/Carbon/Lang/ml.php new file mode 100644 index 00000000000..edbaa83c43b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ml.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - JD Isaacks + */ +return [ + 'year' => ':count വർഷം', + 'a_year' => 'ഒരു വർഷം|:count വർഷം', + 'month' => ':count മാസം', + 'a_month' => 'ഒരു മാസം|:count മാസം', + 'week' => ':count ആഴ്ച', + 'a_week' => 'ഒരാഴ്ച|:count ആഴ്ച', + 'day' => ':count ദിവസം', + 'a_day' => 'ഒരു ദിവസം|:count ദിവസം', + 'hour' => ':count മണിക്കൂർ', + 'a_hour' => 'ഒരു മണിക്കൂർ|:count മണിക്കൂർ', + 'minute' => ':count മിനിറ്റ്', + 'a_minute' => 'ഒരു മിനിറ്റ്|:count മിനിറ്റ്', + 'second' => ':count സെക്കൻഡ്', + 'a_second' => 'അൽപ നിമിഷങ്ങൾ|:count സെക്കൻഡ്', + 'ago' => ':time മുൻപ്', + 'from_now' => ':time കഴിഞ്ഞ്', + 'diff_now' => 'ഇപ്പോൾ', + 'diff_today' => 'ഇന്ന്', + 'diff_yesterday' => 'ഇന്നലെ', + 'diff_tomorrow' => 'നാളെ', + 'formats' => [ + 'LT' => 'A h:mm -നു', + 'LTS' => 'A h:mm:ss -നു', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm -നു', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm -നു', + ], + 'calendar' => [ + 'sameDay' => '[ഇന്ന്] LT', + 'nextDay' => '[നാളെ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ഇന്നലെ] LT', + 'lastWeek' => '[കഴിഞ്ഞ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'രാത്രി'; + } + if ($hour < 12) { + return 'രാവിലെ'; + } + if ($hour < 17) { + return 'ഉച്ച കഴിഞ്ഞ്'; + } + if ($hour < 20) { + return 'വൈകുന്നേരം'; + } + + return 'രാത്രി'; + }, + 'months' => ['ജനുവരി', 'ഫെബ്രുവരി', 'മാർച്ച്', 'ഏപ്രിൽ', 'മേയ്', 'ജൂൺ', 'ജൂലൈ', 'ഓഗസ്റ്റ്', 'സെപ്റ്റംബർ', 'ഒക്ടോബർ', 'നവംബർ', 'ഡിസംബർ'], + 'months_short' => ['ജനു.', 'ഫെബ്രു.', 'മാർ.', 'ഏപ്രി.', 'മേയ്', 'ജൂൺ', 'ജൂലൈ.', 'ഓഗ.', 'സെപ്റ്റ.', 'ഒക്ടോ.', 'നവം.', 'ഡിസം.'], + 'weekdays' => ['ഞായറാഴ്ച', 'തിങ്കളാഴ്ച', 'ചൊവ്വാഴ്ച', 'ബുധനാഴ്ച', 'വ്യാഴാഴ്ച', 'വെള്ളിയാഴ്ച', 'ശനിയാഴ്ച'], + 'weekdays_short' => ['ഞായർ', 'തിങ്കൾ', 'ചൊവ്വ', 'ബുധൻ', 'വ്യാഴം', 'വെള്ളി', 'ശനി'], + 'weekdays_min' => ['ഞാ', 'തി', 'ചൊ', 'ബു', 'വ്യാ', 'വെ', 'ശ'], + 'list' => ', ', + 'weekend' => [0, 0], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ml_IN.php b/libraries/Carbon/src/Carbon/Lang/ml_IN.php new file mode 100644 index 00000000000..e93c73b9052 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ml_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ml.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mn.php b/libraries/Carbon/src/Carbon/Lang/mn.php new file mode 100644 index 00000000000..4012400c1c7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mn.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Zolzaya Erdenebaatar + * - Tom Hughes + * - Akira Matsuda + * - Christopher Dell + * - Michael Kessler + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Ochirkhuyag + * - Batmandakh + * - lucifer-crybaby + */ +return [ + 'year' => ':count жил', + 'y' => ':count жил', + 'month' => ':count сар', + 'm' => ':count сар', + 'week' => ':count долоо хоног', + 'w' => ':count долоо хоног', + 'day' => ':count өдөр', + 'd' => ':count өдөр', + 'hour' => ':count цаг', + 'h' => ':countц', + 'minute' => ':count минут', + 'min' => ':countм', + 'second' => ':count секунд', + 's' => ':countс', + + 'ago_mode' => 'last', + 'ago' => ':time өмнө', + 'year_ago' => ':count жилийн', + 'y_ago' => ':count жилийн', + 'month_ago' => ':count сарын', + 'm_ago' => ':count сарын', + 'day_ago' => ':count хоногийн', + 'd_ago' => ':count хоногийн', + 'week_ago' => ':count долоо хоногийн', + 'w_ago' => ':count долоо хоногийн', + 'hour_ago' => ':count цагийн', + 'minute_ago' => ':count минутын', + 'second_ago' => ':count секундын', + + 'from_now_mode' => 'last', + 'from_now' => 'одоогоос :time', + 'year_from_now' => ':count жилийн дараа', + 'y_from_now' => ':count жилийн дараа', + 'month_from_now' => ':count сарын дараа', + 'm_from_now' => ':count сарын дараа', + 'day_from_now' => ':count хоногийн дараа', + 'd_from_now' => ':count хоногийн дараа', + 'hour_from_now' => ':count цагийн дараа', + 'minute_from_now' => ':count минутын дараа', + 'second_from_now' => ':count секундын дараа', + + 'after_mode' => 'last', + 'after' => ':time дараа', + 'year_after' => ':count жилийн', + 'y_after' => ':count жилийн', + 'month_after' => ':count сарын', + 'm_after' => ':count сарын', + 'day_after' => ':count хоногийн', + 'd_after' => ':count хоногийн', + 'hour_after' => ':count цагийн', + 'minute_after' => ':count минутын', + 'second_after' => ':count секундын', + + 'before_mode' => 'last', + 'before' => ':time өмнө', + 'year_before' => ':count жилийн', + 'y_before' => ':count жилийн', + 'month_before' => ':count сарын', + 'm_before' => ':count сарын', + 'day_before' => ':count хоногийн', + 'd_before' => ':count хоногийн', + 'hour_before' => ':count цагийн', + 'minute_before' => ':count минутын', + 'second_before' => ':count секундын', + + 'list' => ', ', + 'diff_now' => 'одоо', + 'diff_yesterday' => 'өчигдөр', + 'diff_tomorrow' => 'маргааш', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY MMMM DD', + 'LLL' => 'YY-MM-DD, HH:mm', + 'LLLL' => 'YYYY MMMM DD, HH:mm', + ], + 'weekdays' => ['Ням', 'Даваа', 'Мягмар', 'Лхагва', 'Пүрэв', 'Баасан', 'Бямба'], + 'weekdays_short' => ['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя'], + 'weekdays_min' => ['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя'], + 'months' => ['1 сар', '2 сар', '3 сар', '4 сар', '5 сар', '6 сар', '7 сар', '8 сар', '9 сар', '10 сар', '11 сар', '12 сар'], + 'months_short' => ['1 сар', '2 сар', '3 сар', '4 сар', '5 сар', '6 сар', '7 сар', '8 сар', '9 сар', '10 сар', '11 сар', '12 сар'], + 'meridiem' => ['өглөө', 'орой'], + 'first_day_of_week' => 1, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/mn_MN.php b/libraries/Carbon/src/Carbon/Lang/mn_MN.php new file mode 100644 index 00000000000..19683aa60a2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mn_MN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mn.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mni.php b/libraries/Carbon/src/Carbon/Lang/mni.php new file mode 100644 index 00000000000..10e6cf4fa78 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mni.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mni_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mni_IN.php b/libraries/Carbon/src/Carbon/Lang/mni_IN.php new file mode 100644 index 00000000000..a21c1181bf2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mni_IN.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুৱারি', 'ফেব্রুৱারি', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগষ্ট', 'সেপ্তেম্বর', 'ওক্তোবর', 'নবেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জান', 'ফেব', 'মার', 'এপ্রি', 'মে', 'জুন', 'জুল', 'আগ', 'সেপ', 'ওক্ত', 'নবে', 'ডিস'], + 'weekdays' => ['নোংমাইজিং', 'নিংথৌকাবা', 'লৈবাকপোকপা', 'য়ুমশকৈশা', 'শগোলশেন', 'ইরাই', 'থাংজ'], + 'weekdays_short' => ['নোং', 'নিং', 'লৈবাক', 'য়ুম', 'শগোল', 'ইরা', 'থাং'], + 'weekdays_min' => ['নোং', 'নিং', 'লৈবাক', 'য়ুম', 'শগোল', 'ইরা', 'থাং'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['এ.ম.', 'প.ম.'], + + 'year' => ':count ইসিং', // less reliable + 'y' => ':count ইসিং', // less reliable + 'a_year' => ':count ইসিং', // less reliable + + 'second' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable + 's' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable + 'a_second' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mo.php b/libraries/Carbon/src/Carbon/Lang/mo.php new file mode 100644 index 00000000000..4198a5d9fa4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mo.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ro.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mr.php b/libraries/Carbon/src/Carbon/Lang/mr.php new file mode 100644 index 00000000000..3c0cbfc10f0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mr.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Vikram-enyota + */ +return [ + 'year' => ':count वर्ष', + 'y' => ':count वर्ष', + 'month' => ':count महिना|:count महिने', + 'm' => ':count महिना|:count महिने', + 'week' => ':count आठवडा|:count आठवडे', + 'w' => ':count आठवडा|:count आठवडे', + 'day' => ':count दिवस', + 'd' => ':count दिवस', + 'hour' => ':count तास', + 'h' => ':count तास', + 'minute' => ':count मिनिटे', + 'min' => ':count मिनिटे', + 'second' => ':count सेकंद', + 's' => ':count सेकंद', + + 'ago' => ':timeपूर्वी', + 'from_now' => ':timeमध्ये', + 'before' => ':timeपूर्वी', + 'after' => ':timeनंतर', + + 'diff_now' => 'आत्ता', + 'diff_today' => 'आज', + 'diff_yesterday' => 'काल', + 'diff_tomorrow' => 'उद्या', + + 'formats' => [ + 'LT' => 'A h:mm वाजता', + 'LTS' => 'A h:mm:ss वाजता', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm वाजता', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm वाजता', + ], + + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[उद्या] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[काल] LT', + 'lastWeek' => '[मागील] dddd, LT', + 'sameElse' => 'L', + ], + + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'रात्री'; + } + if ($hour < 10) { + return 'सकाळी'; + } + if ($hour < 17) { + return 'दुपारी'; + } + if ($hour < 20) { + return 'सायंकाळी'; + } + + return 'रात्री'; + }, + + 'months' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ऑगस्ट', 'सप्टेंबर', 'ऑक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'months_short' => ['जाने.', 'फेब्रु.', 'मार्च.', 'एप्रि.', 'मे.', 'जून.', 'जुलै.', 'ऑग.', 'सप्टें.', 'ऑक्टो.', 'नोव्हें.', 'डिसें.'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगळवार', 'बुधवार', 'गुरूवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगळ', 'बुध', 'गुरू', 'शुक्र', 'शनि'], + 'weekdays_min' => ['र', 'सो', 'मं', 'बु', 'गु', 'शु', 'श'], + 'list' => [', ', ' आणि '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/mr_IN.php b/libraries/Carbon/src/Carbon/Lang/mr_IN.php new file mode 100644 index 00000000000..fd98c5f83fb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mr_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ms.php b/libraries/Carbon/src/Carbon/Lang/ms.php new file mode 100644 index 00000000000..37c53619a86 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ms.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Azri Jamil + * - JD Isaacks + * - Josh Soref + * - Azri Jamil + * - Hariadi Hinta + * - Ashraf Kamarudin + */ +return [ + 'year' => ':count tahun', + 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'y' => ':count tahun', + 'month' => ':count bulan', + 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'm' => ':count bulan', + 'week' => ':count minggu', + 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'w' => ':count minggu', + 'day' => ':count hari', + 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'd' => ':count hari', + 'hour' => ':count jam', + 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'h' => ':count jam', + 'minute' => ':count minit', + 'a_minute' => '{1}seminit|]1,Inf[:count minit', + 'min' => ':count minit', + 'second' => ':count saat', + 'a_second' => '{1}beberapa saat|]1,Inf[:count saat', + 'millisecond' => ':count milisaat', + 'a_millisecond' => '{1}semilisaat|]1,Inf[:count milliseconds', + 'microsecond' => ':count mikrodetik', + 'a_microsecond' => '{1}semikrodetik|]1,Inf[:count mikrodetik', + 's' => ':count saat', + 'ago' => ':time yang lepas', + 'from_now' => ':time dari sekarang', + 'after' => ':time kemudian', + 'before' => ':time sebelum', + 'diff_now' => 'sekarang', + 'diff_today' => 'Hari', + 'diff_today_regexp' => 'Hari(?:\\s+ini)?(?:\\s+pukul)?', + 'diff_yesterday' => 'semalam', + 'diff_yesterday_regexp' => 'Semalam(?:\\s+pukul)?', + 'diff_tomorrow' => 'esok', + 'diff_tomorrow_regexp' => 'Esok(?:\\s+pukul)?', + 'diff_before_yesterday' => 'kelmarin', + 'diff_after_tomorrow' => 'lusa', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Hari ini pukul] LT', + 'nextDay' => '[Esok pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kelmarin pukul] LT', + 'lastWeek' => 'dddd [lepas pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 1) { + return 'tengah malam'; + } + + if ($hour < 12) { + return 'pagi'; + } + + if ($hour < 13) { + return 'tengah hari'; + } + + if ($hour < 19) { + return 'petang'; + } + + return 'malam'; + }, + 'months' => ['Januari', 'Februari', 'Mac', 'April', 'Mei', 'Jun', 'Julai', 'Ogos', 'September', 'Oktober', 'November', 'Disember'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ogs', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['Ahad', 'Isnin', 'Selasa', 'Rabu', 'Khamis', 'Jumaat', 'Sabtu'], + 'weekdays_short' => ['Ahd', 'Isn', 'Sel', 'Rab', 'Kha', 'Jum', 'Sab'], + 'weekdays_min' => ['Ah', 'Is', 'Sl', 'Rb', 'Km', 'Jm', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' dan '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ms_BN.php b/libraries/Carbon/src/Carbon/Lang/ms_BN.php new file mode 100644 index 00000000000..7bcac3c6327 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ms_BN.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ms.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dd MMMM YYYY, h:mm a', + ], + 'meridiem' => ['a', 'p'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ms_MY.php b/libraries/Carbon/src/Carbon/Lang/ms_MY.php new file mode 100644 index 00000000000..a71cdffc369 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ms_MY.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Azri Jamil + * - JD Isaacks + */ +return require __DIR__.'/ms.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ms_SG.php b/libraries/Carbon/src/Carbon/Lang/ms_SG.php new file mode 100644 index 00000000000..66976c2f0aa --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ms_SG.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ms.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', + ], + 'meridiem' => ['a', 'p'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/mt.php b/libraries/Carbon/src/Carbon/Lang/mt.php new file mode 100644 index 00000000000..b92eeb75906 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mt.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alessandro Maruccia + */ +return [ + 'year' => 'sena|:count sni|:count sni|:count sni', + 'y' => 'sa sena|:count snin|:count snin|:count snin', + 'month' => 'xahar|:count xhur|:count xhur|:count xhur', + 'm' => ':count xahar|:count xhur|:count xhur|:count xhur', + 'week' => 'gimgħa|:count ġimgħat|:count ġimgħat|:count ġimgħat', + 'w' => 'ġimgħa|:count ġimgħat|:count ġimgħat|:count ġimgħat', + 'day' => 'ġurnata|:count ġranet|:count ġranet|:count ġranet', + 'd' => 'ġurnata|:count ġranet|:count ġranet|:count ġranet', + 'hour' => 'siegħa|:count siegħat|:count siegħat|:count siegħat', + 'h' => 'siegħa|:count sigħat|:count sigħat|:count sigħat', + 'minute' => 'minuta|:count minuti|:count minuti|:count minuti', + 'min' => 'min.|:count min.|:count min.|:count min.', + 'second' => 'ftit sekondi|:count sekondi|:count sekondi|:count sekondi', + 's' => 'sek.|:count sek.|:count sek.|:count sek.', + 'ago' => ':time ilu', + 'from_now' => 'f’ :time', + 'diff_now' => 'issa', + 'diff_today' => 'Illum', + 'diff_today_regexp' => 'Illum(?:\\s+fil-)?', + 'diff_yesterday' => 'lbieraħ', + 'diff_yesterday_regexp' => 'Il-bieraħ(?:\\s+fil-)?', + 'diff_tomorrow' => 'għada', + 'diff_tomorrow_regexp' => 'Għada(?:\\s+fil-)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Illum fil-]LT', + 'nextDay' => '[Għada fil-]LT', + 'nextWeek' => 'dddd [fil-]LT', + 'lastDay' => '[Il-bieraħ fil-]LT', + 'lastWeek' => 'dddd [li għadda] [fil-]LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Jannar', 'Frar', 'Marzu', 'April', 'Mejju', 'Ġunju', 'Lulju', 'Awwissu', 'Settembru', 'Ottubru', 'Novembru', 'Diċembru'], + 'months_short' => ['Jan', 'Fra', 'Mar', 'Apr', 'Mej', 'Ġun', 'Lul', 'Aww', 'Set', 'Ott', 'Nov', 'Diċ'], + 'weekdays' => ['Il-Ħadd', 'It-Tnejn', 'It-Tlieta', 'L-Erbgħa', 'Il-Ħamis', 'Il-Ġimgħa', 'Is-Sibt'], + 'weekdays_short' => ['Ħad', 'Tne', 'Tli', 'Erb', 'Ħam', 'Ġim', 'Sib'], + 'weekdays_min' => ['Ħa', 'Tn', 'Tl', 'Er', 'Ħa', 'Ġi', 'Si'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' u '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/mt_MT.php b/libraries/Carbon/src/Carbon/Lang/mt_MT.php new file mode 100644 index 00000000000..ed14cd83ecb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mt_MT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mua.php b/libraries/Carbon/src/Carbon/Lang/mua.php new file mode 100644 index 00000000000..27d64147b75 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mua.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['comme', 'lilli'], + 'weekdays' => ['Com’yakke', 'Comlaaɗii', 'Comzyiiɗii', 'Comkolle', 'Comkaldǝɓlii', 'Comgaisuu', 'Comzyeɓsuu'], + 'weekdays_short' => ['Cya', 'Cla', 'Czi', 'Cko', 'Cka', 'Cga', 'Cze'], + 'weekdays_min' => ['Cya', 'Cla', 'Czi', 'Cko', 'Cka', 'Cga', 'Cze'], + 'months' => ['Fĩi Loo', 'Cokcwaklaŋne', 'Cokcwaklii', 'Fĩi Marfoo', 'Madǝǝuutǝbijaŋ', 'Mamǝŋgwãafahbii', 'Mamǝŋgwãalii', 'Madǝmbii', 'Fĩi Dǝɓlii', 'Fĩi Mundaŋ', 'Fĩi Gwahlle', 'Fĩi Yuru'], + 'months_short' => ['FLO', 'CLA', 'CKI', 'FMF', 'MAD', 'MBI', 'MLI', 'MAM', 'FDE', 'FMU', 'FGW', 'FYU'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/my.php b/libraries/Carbon/src/Carbon/Lang/my.php new file mode 100644 index 00000000000..f6bcea1705d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/my.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + * - Nay Lin Aung + */ +return [ + 'year' => '{1}တစ်နှစ်|]1,Inf[:count နှစ်', + 'y' => ':count နှစ်', + 'month' => '{1}တစ်လ|]1,Inf[:count လ', + 'm' => ':count လ', + 'week' => ':count ပတ်', + 'w' => ':count ပတ်', + 'day' => '{1}တစ်ရက်|]1,Inf[:count ရက်', + 'd' => ':count ရက်', + 'hour' => '{1}တစ်နာရီ|]1,Inf[:count နာရီ', + 'h' => ':count နာရီ', + 'minute' => '{1}တစ်မိနစ်|]1,Inf[:count မိနစ်', + 'min' => ':count မိနစ်', + 'second' => '{1}စက္ကန်.အနည်းငယ်|]1,Inf[:count စက္ကန့်', + 's' => ':count စက္ကန့်', + 'ago' => 'လွန်ခဲ့သော :time က', + 'from_now' => 'လာမည့် :time မှာ', + 'after' => ':time ကြာပြီးနောက်', + 'before' => ':time မတိုင်ခင်', + 'diff_now' => 'အခုလေးတင်', + 'diff_today' => 'ယနေ.', + 'diff_yesterday' => 'မနေ့က', + 'diff_yesterday_regexp' => 'မနေ.က', + 'diff_tomorrow' => 'မနက်ဖြန်', + 'diff_before_yesterday' => 'တမြန်နေ့က', + 'diff_after_tomorrow' => 'တဘက်ခါ', + 'period_recurrences' => ':count ကြိမ်', + 'formats' => [ + 'LT' => 'Oh:Om A', + 'LTS' => 'Oh:Om:Os A', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY Oh:Om A', + 'LLLL' => 'dddd OD MMMM OY Oh:Om A', + ], + 'calendar' => [ + 'sameDay' => '[ယနေ.] LT [မှာ]', + 'nextDay' => '[မနက်ဖြန်] LT [မှာ]', + 'nextWeek' => 'dddd LT [မှာ]', + 'lastDay' => '[မနေ.က] LT [မှာ]', + 'lastWeek' => '[ပြီးခဲ့သော] dddd LT [မှာ]', + 'sameElse' => 'L', + ], + 'months' => ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်', 'ဇူလိုင်', 'သြဂုတ်', 'စက်တင်ဘာ', 'အောက်တိုဘာ', 'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'], + 'months_short' => ['ဇန်', 'ဖေ', 'မတ်', 'ပြီ', 'မေ', 'ဇွန်', 'လိုင်', 'သြ', 'စက်', 'အောက်', 'နို', 'ဒီ'], + 'weekdays' => ['တနင်္ဂနွေ', 'တနင်္လာ', 'အင်္ဂါ', 'ဗုဒ္ဓဟူး', 'ကြာသပတေး', 'သောကြာ', 'စနေ'], + 'weekdays_short' => ['နွေ', 'လာ', 'ဂါ', 'ဟူး', 'ကြာ', 'သော', 'နေ'], + 'weekdays_min' => ['နွေ', 'လာ', 'ဂါ', 'ဟူး', 'ကြာ', 'သော', 'နေ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'alt_numbers' => ['၀၀', '၀၁', '၀၂', '၀၃', '၀၄', '၀၅', '၀၆', '၀၇', '၀၈', '၀၉', '၁၀', '၁၁', '၁၂', '၁၃', '၁၄', '၁၅', '၁၆', '၁၇', '၁၈', '၁၉', '၂၀', '၂၁', '၂၂', '၂၃', '၂၄', '၂၅', '၂၆', '၂၇', '၂၈', '၂၉', '၃၀', '၃၁', '၃၂', '၃၃', '၃၄', '၃၅', '၃၆', '၃၇', '၃၈', '၃၉', '၄၀', '၄၁', '၄၂', '၄၃', '၄၄', '၄၅', '၄၆', '၄၇', '၄၈', '၄၉', '၅၀', '၅၁', '၅၂', '၅၃', '၅၄', '၅၅', '၅၆', '၅၇', '၅၈', '၅၉', '၆၀', '၆၁', '၆၂', '၆၃', '၆၄', '၆၅', '၆၆', '၆၇', '၆၈', '၆၉', '၇၀', '၇၁', '၇၂', '၇၃', '၇၄', '၇၅', '၇၆', '၇၇', '၇၈', '၇၉', '၈၀', '၈၁', '၈၂', '၈၃', '၈၄', '၈၅', '၈၆', '၈၇', '၈၈', '၈၉', '၉၀', '၉၁', '၉၂', '၉၃', '၉၄', '၉၅', '၉၆', '၉၇', '၉၈', '၉၉'], + 'meridiem' => ['နံနက်', 'ညနေ'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/my_MM.php b/libraries/Carbon/src/Carbon/Lang/my_MM.php new file mode 100644 index 00000000000..859678a9cfe --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/my_MM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/my.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/mzn.php b/libraries/Carbon/src/Carbon/Lang/mzn.php new file mode 100644 index 00000000000..65c7fc2bbbd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/mzn.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'first_day_of_week' => 6, + 'weekend' => [5, 5], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nan.php b/libraries/Carbon/src/Carbon/Lang/nan.php new file mode 100644 index 00000000000..e9ae867f780 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nan.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nan_TW.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nan_TW.php b/libraries/Carbon/src/Carbon/Lang/nan_TW.php new file mode 100644 index 00000000000..978db077140 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nan_TW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['禮拜日', '禮拜一', '禮拜二', '禮拜三', '禮拜四', '禮拜五', '禮拜六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['頂晡', '下晡'], + + 'year' => ':count 年', + 'y' => ':count 年', + 'a_year' => ':count 年', + + 'month' => ':count goe̍h', + 'm' => ':count goe̍h', + 'a_month' => ':count goe̍h', + + 'week' => ':count lé-pài', + 'w' => ':count lé-pài', + 'a_week' => ':count lé-pài', + + 'day' => ':count 日', + 'd' => ':count 日', + 'a_day' => ':count 日', + + 'hour' => ':count tiám-cheng', + 'h' => ':count tiám-cheng', + 'a_hour' => ':count tiám-cheng', + + 'minute' => ':count Hun-cheng', + 'min' => ':count Hun-cheng', + 'a_minute' => ':count Hun-cheng', + + 'second' => ':count Bió', + 's' => ':count Bió', + 'a_second' => ':count Bió', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nan_TW@latin.php b/libraries/Carbon/src/Carbon/Lang/nan_TW@latin.php new file mode 100644 index 00000000000..4376111f1f1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nan_TW@latin.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Arne Goetje arne@canonical.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['1goe̍h', '2goe̍h', '3goe̍h', '4goe̍h', '5goe̍h', '6goe̍h', '7goe̍h', '8goe̍h', '9goe̍h', '10goe̍h', '11goe̍h', '12goe̍h'], + 'months_short' => ['1g', '2g', '3g', '4g', '5g', '6g', '7g', '8g', '9g', '10g', '11g', '12g'], + 'weekdays' => ['lé-pài-ji̍t', 'pài-it', 'pài-jī', 'pài-saⁿ', 'pài-sì', 'pài-gō͘', 'pài-la̍k'], + 'weekdays_short' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'weekdays_min' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['téng-po͘', 'ē-po͘'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/naq.php b/libraries/Carbon/src/Carbon/Lang/naq.php new file mode 100644 index 00000000000..7040f5dad93 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/naq.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ǁgoagas', 'ǃuias'], + 'weekdays' => ['Sontaxtsees', 'Mantaxtsees', 'Denstaxtsees', 'Wunstaxtsees', 'Dondertaxtsees', 'Fraitaxtsees', 'Satertaxtsees'], + 'weekdays_short' => ['Son', 'Ma', 'De', 'Wu', 'Do', 'Fr', 'Sat'], + 'weekdays_min' => ['Son', 'Ma', 'De', 'Wu', 'Do', 'Fr', 'Sat'], + 'months' => ['ǃKhanni', 'ǃKhanǀgôab', 'ǀKhuuǁkhâb', 'ǃHôaǂkhaib', 'ǃKhaitsâb', 'Gamaǀaeb', 'ǂKhoesaob', 'Aoǁkhuumûǁkhâb', 'Taraǀkhuumûǁkhâb', 'ǂNûǁnâiseb', 'ǀHooǂgaeb', 'Hôasoreǁkhâb'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count kurigu', + 'y' => ':count kurigu', + 'a_year' => ':count kurigu', + + 'month' => ':count ǁaub', // less reliable + 'm' => ':count ǁaub', // less reliable + 'a_month' => ':count ǁaub', // less reliable + + 'week' => ':count hû', // less reliable + 'w' => ':count hû', // less reliable + 'a_week' => ':count hû', // less reliable + + 'day' => ':count ǀhobas', // less reliable + 'd' => ':count ǀhobas', // less reliable + 'a_day' => ':count ǀhobas', // less reliable + + 'hour' => ':count ǂgaes', // less reliable + 'h' => ':count ǂgaes', // less reliable + 'a_hour' => ':count ǂgaes', // less reliable + + 'minute' => ':count minutga', // less reliable + 'min' => ':count minutga', // less reliable + 'a_minute' => ':count minutga', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nb.php b/libraries/Carbon/src/Carbon/Lang/nb.php new file mode 100644 index 00000000000..2dc79b7dfe1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nb.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Alexander Tømmerås + * - Sigurd Gartmann + * - JD Isaacks + */ +return [ + 'year' => ':count år|:count år', + 'a_year' => 'ett år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count måned|:count måneder', + 'a_month' => 'en måned|:count måneder', + 'm' => ':count md.', + 'week' => ':count uke|:count uker', + 'a_week' => 'en uke|:count uker', + 'w' => ':count u.', + 'day' => ':count dag|:count dager', + 'a_day' => 'en dag|:count dager', + 'd' => ':count d.', + 'hour' => ':count time|:count timer', + 'a_hour' => 'en time|:count timer', + 'h' => ':count t', + 'minute' => ':count minutt|:count minutter', + 'a_minute' => 'ett minutt|:count minutter', + 'min' => ':count min', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'noen sekunder|:count sekunder', + 's' => ':count sek', + 'ago' => ':time siden', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time før', + 'diff_now' => 'akkurat nå', + 'diff_today' => 'i dag', + 'diff_today_regexp' => 'i dag(?:\\s+kl.)?', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'i går(?:\\s+kl.)?', + 'diff_tomorrow' => 'i morgen', + 'diff_tomorrow_regexp' => 'i morgen(?:\\s+kl.)?', + 'diff_before_yesterday' => 'i forgårs', + 'diff_after_tomorrow' => 'i overmorgen', + 'period_recurrences' => 'en gang|:count ganger', + 'period_interval' => 'hver :interval', + 'period_start_date' => 'fra :date', + 'period_end_date' => 'til :date', + 'months' => ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'], + 'weekdays_short' => ['søn', 'man', 'tir', 'ons', 'tor', 'fre', 'lør'], + 'weekdays_min' => ['sø', 'ma', 'ti', 'on', 'to', 'fr', 'lø'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY [kl.] HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i dag kl.] LT', + 'nextDay' => '[i morgen kl.] LT', + 'nextWeek' => 'dddd [kl.] LT', + 'lastDay' => '[i går kl.] LT', + 'lastWeek' => '[forrige] dddd [kl.] LT', + 'sameElse' => 'L', + ], + 'list' => [', ', ' og '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/nb_NO.php b/libraries/Carbon/src/Carbon/Lang/nb_NO.php new file mode 100644 index 00000000000..0d8ae4388e7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nb_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nb.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nb_SJ.php b/libraries/Carbon/src/Carbon/Lang/nb_SJ.php new file mode 100644 index 00000000000..121bb21a89f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nb_SJ.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/nb.php', [ + 'formats' => [ + 'LL' => 'D. MMM YYYY', + 'LLL' => 'D. MMMM YYYY, HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY, HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nd.php b/libraries/Carbon/src/Carbon/Lang/nd.php new file mode 100644 index 00000000000..a0d067be5ae --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nd.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sonto', 'Mvulo', 'Sibili', 'Sithathu', 'Sine', 'Sihlanu', 'Mgqibelo'], + 'weekdays_short' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], + 'weekdays_min' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], + 'months' => ['Zibandlela', 'Nhlolanja', 'Mbimbitho', 'Mabasa', 'Nkwenkwezi', 'Nhlangula', 'Ntulikazi', 'Ncwabakazi', 'Mpandula', 'Mfumfu', 'Lwezi', 'Mpalakazi'], + 'months_short' => ['Zib', 'Nhlo', 'Mbi', 'Mab', 'Nkw', 'Nhla', 'Ntu', 'Ncw', 'Mpan', 'Mfu', 'Lwe', 'Mpal'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => 'okweminyaka engu-:count', // less reliable + 'y' => 'okweminyaka engu-:count', // less reliable + 'a_year' => 'okweminyaka engu-:count', // less reliable + + 'month' => 'inyanga ezingu-:count', + 'm' => 'inyanga ezingu-:count', + 'a_month' => 'inyanga ezingu-:count', + + 'week' => 'amaviki angu-:count', + 'w' => 'amaviki angu-:count', + 'a_week' => 'amaviki angu-:count', + + 'day' => 'kwamalanga angu-:count', + 'd' => 'kwamalanga angu-:count', + 'a_day' => 'kwamalanga angu-:count', + + 'hour' => 'amahola angu-:count', + 'h' => 'amahola angu-:count', + 'a_hour' => 'amahola angu-:count', + + 'minute' => 'imizuzu engu-:count', + 'min' => 'imizuzu engu-:count', + 'a_minute' => 'imizuzu engu-:count', + + 'second' => 'imizuzwana engu-:count', + 's' => 'imizuzwana engu-:count', + 'a_second' => 'imizuzwana engu-:count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nds.php b/libraries/Carbon/src/Carbon/Lang/nds.php new file mode 100644 index 00000000000..ccdc5beb40f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nds.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nds_DE.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nds_DE.php b/libraries/Carbon/src/Carbon/Lang/nds_DE.php new file mode 100644 index 00000000000..a79ce7e5f51 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nds_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jannuaar', 'Feberwaar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'weekdays' => ['Sünndag', 'Maandag', 'Dingsdag', 'Middeweek', 'Dunnersdag', 'Freedag', 'Sünnavend'], + 'weekdays_short' => ['Sdag', 'Maan', 'Ding', 'Midd', 'Dunn', 'Free', 'Svd.'], + 'weekdays_min' => ['Sd', 'Ma', 'Di', 'Mi', 'Du', 'Fr', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count Johr', + 'y' => ':countJ', + 'a_year' => '{1}een Johr|:count Johr', + + 'month' => ':count Maand', + 'm' => ':countM', + 'a_month' => '{1}een Maand|:count Maand', + + 'week' => ':count Week|:count Weken', + 'w' => ':countW', + 'a_week' => '{1}een Week|:count Week|:count Weken', + + 'day' => ':count Dag|:count Daag', + 'd' => ':countD', + 'a_day' => '{1}een Dag|:count Dag|:count Daag', + + 'hour' => ':count Stünn|:count Stünnen', + 'h' => ':countSt', + 'a_hour' => '{1}een Stünn|:count Stünn|:count Stünnen', + + 'minute' => ':count Minuut|:count Minuten', + 'min' => ':countm', + 'a_minute' => '{1}een Minuut|:count Minuut|:count Minuten', + + 'second' => ':count Sekunn|:count Sekunnen', + 's' => ':counts', + 'a_second' => 'en poor Sekunnen|:count Sekunn|:count Sekunnen', + + 'ago' => 'vör :time', + 'from_now' => 'in :time', + 'before' => ':time vörher', + 'after' => ':time later', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nds_NL.php b/libraries/Carbon/src/Carbon/Lang/nds_NL.php new file mode 100644 index 00000000000..904879caf9d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nds_NL.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jaunuwoa', 'Februwoa', 'Moaz', 'Aprell', 'Mai', 'Juni', 'Juli', 'August', 'Septamba', 'Oktoba', 'Nowamba', 'Dezamba'], + 'months_short' => ['Jan', 'Feb', 'Moz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Now', 'Dez'], + 'weekdays' => ['Sinndag', 'Mondag', 'Dingsdag', 'Meddwäakj', 'Donnadag', 'Friedag', 'Sinnowend'], + 'weekdays_short' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'weekdays_min' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ne.php b/libraries/Carbon/src/Carbon/Lang/ne.php new file mode 100644 index 00000000000..bcb045ca595 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ne.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - nootanghimire + * - Josh Soref + * - Nj Subedi + * - JD Isaacks + */ +return [ + 'year' => 'एक बर्ष|:count बर्ष', + 'y' => ':count वर्ष', + 'month' => 'एक महिना|:count महिना', + 'm' => ':count महिना', + 'week' => ':count हप्ता', + 'w' => ':count हप्ता', + 'day' => 'एक दिन|:count दिन', + 'd' => ':count दिन', + 'hour' => 'एक घण्टा|:count घण्टा', + 'h' => ':count घण्टा', + 'minute' => 'एक मिनेट|:count मिनेट', + 'min' => ':count मिनेट', + 'second' => 'केही क्षण|:count सेकेण्ड', + 's' => ':count सेकेण्ड', + 'ago' => ':time अगाडि', + 'from_now' => ':timeमा', + 'after' => ':time पछि', + 'before' => ':time अघि', + 'diff_now' => 'अहिले', + 'diff_today' => 'आज', + 'diff_yesterday' => 'हिजो', + 'diff_tomorrow' => 'भोलि', + 'formats' => [ + 'LT' => 'Aको h:mm बजे', + 'LTS' => 'Aको h:mm:ss बजे', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, Aको h:mm बजे', + 'LLLL' => 'dddd, D MMMM YYYY, Aको h:mm बजे', + ], + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[भोलि] LT', + 'nextWeek' => '[आउँदो] dddd[,] LT', + 'lastDay' => '[हिजो] LT', + 'lastWeek' => '[गएको] dddd[,] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 3) { + return 'राति'; + } + if ($hour < 12) { + return 'बिहान'; + } + if ($hour < 16) { + return 'दिउँसो'; + } + if ($hour < 20) { + return 'साँझ'; + } + + return 'राति'; + }, + 'months' => ['जनवरी', 'फेब्रुवरी', 'मार्च', 'अप्रिल', 'मई', 'जुन', 'जुलाई', 'अगष्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'months_short' => ['जन.', 'फेब्रु.', 'मार्च', 'अप्रि.', 'मई', 'जुन', 'जुलाई.', 'अग.', 'सेप्ट.', 'अक्टो.', 'नोभे.', 'डिसे.'], + 'weekdays' => ['आइतबार', 'सोमबार', 'मङ्गलबार', 'बुधबार', 'बिहिबार', 'शुक्रबार', 'शनिबार'], + 'weekdays_short' => ['आइत.', 'सोम.', 'मङ्गल.', 'बुध.', 'बिहि.', 'शुक्र.', 'शनि.'], + 'weekdays_min' => ['आ.', 'सो.', 'मं.', 'बु.', 'बि.', 'शु.', 'श.'], + 'list' => [', ', ' र '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ne_IN.php b/libraries/Carbon/src/Carbon/Lang/ne_IN.php new file mode 100644 index 00000000000..a036efd712b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ne_IN.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ne.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'yy/M/d', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D, h:mm a', + 'LLLL' => 'YYYY MMMM D, dddd, h:mm a', + ], + 'months' => ['जनवरी', 'फेब्रुअरी', 'मार्च', 'अप्रिल', 'मे', 'जुन', 'जुलाई', 'अगस्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'months_short' => ['जनवरी', 'फेब्रुअरी', 'मार्च', 'अप्रिल', 'मे', 'जुन', 'जुलाई', 'अगस्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'weekend' => [0, 0], + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ne_NP.php b/libraries/Carbon/src/Carbon/Lang/ne_NP.php new file mode 100644 index 00000000000..13e3db0e0e0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ne_NP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ne.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nhn.php b/libraries/Carbon/src/Carbon/Lang/nhn.php new file mode 100644 index 00000000000..ba037cc5581 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nhn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nhn_MX.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nhn_MX.php b/libraries/Carbon/src/Carbon/Lang/nhn_MX.php new file mode 100644 index 00000000000..38476cbcd55 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nhn_MX.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'weekdays' => ['teoilhuitl', 'ceilhuitl', 'omeilhuitl', 'yeilhuitl', 'nahuilhuitl', 'macuililhuitl', 'chicuaceilhuitl'], + 'weekdays_short' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'weekdays_min' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'day_of_first_week_of_year' => 1, + + 'month' => ':count metztli', // less reliable + 'm' => ':count metztli', // less reliable + 'a_month' => ':count metztli', // less reliable + + 'week' => ':count tonalli', // less reliable + 'w' => ':count tonalli', // less reliable + 'a_week' => ':count tonalli', // less reliable + + 'day' => ':count tonatih', // less reliable + 'd' => ':count tonatih', // less reliable + 'a_day' => ':count tonatih', // less reliable + + 'minute' => ':count toltecayotl', // less reliable + 'min' => ':count toltecayotl', // less reliable + 'a_minute' => ':count toltecayotl', // less reliable + + 'second' => ':count ome', // less reliable + 's' => ':count ome', // less reliable + 'a_second' => ':count ome', // less reliable + + 'year' => ':count xihuitl', + 'y' => ':count xihuitl', + 'a_year' => ':count xihuitl', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/niu.php b/libraries/Carbon/src/Carbon/Lang/niu.php new file mode 100644 index 00000000000..6ebaa64987d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/niu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/niu_NU.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/niu_NU.php b/libraries/Carbon/src/Carbon/Lang/niu_NU.php new file mode 100644 index 00000000000..e44740d35ed --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/niu_NU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RockET Systems Emani Fakaotimanava-Lui emani@niue.nu + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Ianuali', 'Fepuali', 'Masi', 'Apelila', 'Me', 'Iuni', 'Iulai', 'Aokuso', 'Sepetema', 'Oketopa', 'Novema', 'Tesemo'], + 'months_short' => ['Ian', 'Fep', 'Mas', 'Ape', 'Me', 'Iun', 'Iul', 'Aok', 'Sep', 'Oke', 'Nov', 'Tes'], + 'weekdays' => ['Aho Tapu', 'Aho Gofua', 'Aho Ua', 'Aho Lotu', 'Aho Tuloto', 'Aho Falaile', 'Aho Faiumu'], + 'weekdays_short' => ['Tapu', 'Gofua', 'Ua', 'Lotu', 'Tuloto', 'Falaile', 'Faiumu'], + 'weekdays_min' => ['Tapu', 'Gofua', 'Ua', 'Lotu', 'Tuloto', 'Falaile', 'Faiumu'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count tau', + 'y' => ':count tau', + 'a_year' => ':count tau', + + 'month' => ':count mahina', + 'm' => ':count mahina', + 'a_month' => ':count mahina', + + 'week' => ':count faahi tapu', + 'w' => ':count faahi tapu', + 'a_week' => ':count faahi tapu', + + 'day' => ':count aho', + 'd' => ':count aho', + 'a_day' => ':count aho', + + 'hour' => ':count e tulā', + 'h' => ':count e tulā', + 'a_hour' => ':count e tulā', + + 'minute' => ':count minuti', + 'min' => ':count minuti', + 'a_minute' => ':count minuti', + + 'second' => ':count sekone', + 's' => ':count sekone', + 'a_second' => ':count sekone', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nl.php b/libraries/Carbon/src/Carbon/Lang/nl.php new file mode 100644 index 00000000000..1367bcff620 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nl.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Kevin Huang + * - Jacob Middag + * - JD Isaacks + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Jacob Middag + * - JD Isaacks + * - Propaganistas + * - MegaXLR + * - adriaanzon + * - MonkeyPhysics + * - JeroenG + * - RikSomers + * - proclame + * - Rik de Groot (hwdegroot) + */ +return [ + 'year' => ':count jaar|:count jaar', + 'a_year' => 'een jaar|:count jaar', + 'y' => ':countj', + 'month' => ':count maand|:count maanden', + 'a_month' => 'een maand|:count maanden', + 'm' => ':countmnd', + 'week' => ':count week|:count weken', + 'a_week' => 'een week|:count weken', + 'w' => ':countw', + 'day' => ':count dag|:count dagen', + 'a_day' => 'een dag|:count dagen', + 'd' => ':countd', + 'hour' => ':count uur|:count uur', + 'a_hour' => 'een uur|:count uur', + 'h' => ':countu', + 'minute' => ':count minuut|:count minuten', + 'a_minute' => 'een minuut|:count minuten', + 'min' => ':countmin', + 'second' => ':count seconde|:count seconden', + 'a_second' => 'een paar seconden|:count seconden', + 's' => ':counts', + 'ago' => ':time geleden', + 'from_now' => 'over :time', + 'after' => ':time later', + 'before' => ':time eerder', + 'diff_now' => 'nu', + 'diff_today' => 'vandaag', + 'diff_today_regexp' => 'vandaag(?:\\s+om)?', + 'diff_yesterday' => 'gisteren', + 'diff_yesterday_regexp' => 'gisteren(?:\\s+om)?', + 'diff_tomorrow' => 'morgen', + 'diff_tomorrow_regexp' => 'morgen(?:\\s+om)?', + 'diff_after_tomorrow' => 'overmorgen', + 'diff_before_yesterday' => 'eergisteren', + 'period_recurrences' => ':count keer', + 'period_interval' => function (string $interval = '') { + /** @var string $output */ + $output = preg_replace('/^(een|één|1)\s+/u', '', $interval); + + if (preg_match('/^(een|één|1)( jaar|j| uur|u)/u', $interval)) { + return "elk $output"; + } + + return "elke $output"; + }, + 'period_start_date' => 'van :date', + 'period_end_date' => 'tot :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[vandaag om] LT', + 'nextDay' => '[morgen om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[gisteren om] LT', + 'lastWeek' => '[afgelopen] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'mmm_suffix' => '.', + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo.', 'ma.', 'di.', 'wo.', 'do.', 'vr.', 'za.'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], + 'meridiem' => ['\'s ochtends', '\'s middags'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/nl_AW.php b/libraries/Carbon/src/Carbon/Lang/nl_AW.php new file mode 100644 index 00000000000..6988fdc4ec1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nl_AW.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'formats' => [ + 'L' => 'DD-MM-YY', + ], + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nl_BE.php b/libraries/Carbon/src/Carbon/Lang/nl_BE.php new file mode 100644 index 00000000000..c2763464bf6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nl_BE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Kevin Huang + * - Jacob Middag + * - JD Isaacks + * - Propaganistas + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nl_BQ.php b/libraries/Carbon/src/Carbon/Lang/nl_BQ.php new file mode 100644 index 00000000000..dd4268aed06 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nl_BQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nl_CW.php b/libraries/Carbon/src/Carbon/Lang/nl_CW.php new file mode 100644 index 00000000000..dd4268aed06 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nl_CW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nl_NL.php b/libraries/Carbon/src/Carbon/Lang/nl_NL.php new file mode 100644 index 00000000000..536c874ea3f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nl_NL.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nl_SR.php b/libraries/Carbon/src/Carbon/Lang/nl_SR.php new file mode 100644 index 00000000000..dd4268aed06 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nl_SR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nl_SX.php b/libraries/Carbon/src/Carbon/Lang/nl_SX.php new file mode 100644 index 00000000000..dd4268aed06 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nl_SX.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nmg.php b/libraries/Carbon/src/Carbon/Lang/nmg.php new file mode 100644 index 00000000000..6beb9387a61 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nmg.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['maná', 'kugú'], + 'weekdays' => ['sɔ́ndɔ', 'mɔ́ndɔ', 'sɔ́ndɔ mafú mába', 'sɔ́ndɔ mafú málal', 'sɔ́ndɔ mafú mána', 'mabágá má sukul', 'sásadi'], + 'weekdays_short' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'mbs', 'sas'], + 'weekdays_min' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'mbs', 'sas'], + 'months' => ['ngwɛn matáhra', 'ngwɛn ńmba', 'ngwɛn ńlal', 'ngwɛn ńna', 'ngwɛn ńtan', 'ngwɛn ńtuó', 'ngwɛn hɛmbuɛrí', 'ngwɛn lɔmbi', 'ngwɛn rɛbvuâ', 'ngwɛn wum', 'ngwɛn wum navǔr', 'krísimin'], + 'months_short' => ['ng1', 'ng2', 'ng3', 'ng4', 'ng5', 'ng6', 'ng7', 'ng8', 'ng9', 'ng10', 'ng11', 'kris'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nn.php b/libraries/Carbon/src/Carbon/Lang/nn.php new file mode 100644 index 00000000000..cd2c386c8bf --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nn.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Alexander Tømmerås + * - Øystein + * - JD Isaacks + * - Gaute Hvoslef Kvalnes (gaute) + */ +return [ + 'year' => ':count år', + 'a_year' => 'eit år|:count år', + 'y' => ':count år', + 'month' => ':count månad|:count månader', + 'a_month' => 'ein månad|:count månader', + 'm' => ':count md', + 'week' => ':count veke|:count veker', + 'a_week' => 'ei veke|:count veker', + 'w' => ':countv', + 'day' => ':count dag|:count dagar', + 'a_day' => 'ein dag|:count dagar', + 'd' => ':countd', + 'hour' => ':count time|:count timar', + 'a_hour' => 'ein time|:count timar', + 'h' => ':countt', + 'minute' => ':count minutt', + 'a_minute' => 'eit minutt|:count minutt', + 'min' => ':countm', + 'second' => ':count sekund', + 'a_second' => 'nokre sekund|:count sekund', + 's' => ':counts', + 'ago' => ':time sidan', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time før', + 'diff_today' => 'I dag', + 'diff_yesterday' => 'I går', + 'diff_yesterday_regexp' => 'I går(?:\\s+klokka)?', + 'diff_tomorrow' => 'I morgon', + 'diff_tomorrow_regexp' => 'I morgon(?:\\s+klokka)?', + 'diff_today_regexp' => 'I dag(?:\\s+klokka)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY [kl.] H:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[I dag klokka] LT', + 'nextDay' => '[I morgon klokka] LT', + 'nextWeek' => 'dddd [klokka] LT', + 'lastDay' => '[I går klokka] LT', + 'lastWeek' => '[Føregåande] dddd [klokka] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sundag', 'måndag', 'tysdag', 'onsdag', 'torsdag', 'fredag', 'laurdag'], + 'weekdays_short' => ['sun', 'mån', 'tys', 'ons', 'tor', 'fre', 'lau'], + 'weekdays_min' => ['su', 'må', 'ty', 'on', 'to', 'fr', 'la'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], + 'meridiem' => ['f.m.', 'e.m.'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/nn_NO.php b/libraries/Carbon/src/Carbon/Lang/nn_NO.php new file mode 100644 index 00000000000..dd00a0e55ab --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nn_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nn.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nnh.php b/libraries/Carbon/src/Carbon/Lang/nnh.php new file mode 100644 index 00000000000..d283ed952e5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nnh.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['mbaʼámbaʼ', 'ncwònzém'], + 'weekdays' => null, + 'weekdays_short' => ['lyɛʼɛ́ sẅíŋtè', 'mvfò lyɛ̌ʼ', 'mbɔ́ɔntè mvfò lyɛ̌ʼ', 'tsètsɛ̀ɛ lyɛ̌ʼ', 'mbɔ́ɔntè tsetsɛ̀ɛ lyɛ̌ʼ', 'mvfò màga lyɛ̌ʼ', 'màga lyɛ̌ʼ'], + 'weekdays_min' => null, + 'months' => null, + 'months_short' => ['saŋ tsetsɛ̀ɛ lùm', 'saŋ kàg ngwóŋ', 'saŋ lepyè shúm', 'saŋ cÿó', 'saŋ tsɛ̀ɛ cÿó', 'saŋ njÿoláʼ', 'saŋ tyɛ̀b tyɛ̀b mbʉ̀ŋ', 'saŋ mbʉ̀ŋ', 'saŋ ngwɔ̀ʼ mbÿɛ', 'saŋ tàŋa tsetsáʼ', 'saŋ mejwoŋó', 'saŋ lùm'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => '[lyɛ]̌ʼ d [na] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd , [lyɛ]̌ʼ d [na] MMMM, YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/no.php b/libraries/Carbon/src/Carbon/Lang/no.php new file mode 100644 index 00000000000..c06ed07d080 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/no.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Daniel S. Billing + * - Paul + * - Jimmie Johansson + * - Jens Herlevsen + */ +return array_replace_recursive(require __DIR__.'/nb.php', [ + 'formats' => [ + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'nextWeek' => 'på dddd [kl.] LT', + 'lastWeek' => '[i] dddd[s kl.] LT', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nr.php b/libraries/Carbon/src/Carbon/Lang/nr.php new file mode 100644 index 00000000000..b37dee54d05 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nr_ZA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nr_ZA.php b/libraries/Carbon/src/Carbon/Lang/nr_ZA.php new file mode 100644 index 00000000000..28d154a3b06 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nr_ZA.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janabari', 'uFeberbari', 'uMatjhi', 'u-Apreli', 'Meyi', 'Juni', 'Julayi', 'Arhostosi', 'Septemba', 'Oktoba', 'Usinyikhaba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mat', 'Apr', 'Mey', 'Jun', 'Jul', 'Arh', 'Sep', 'Okt', 'Usi', 'Dis'], + 'weekdays' => ['uSonto', 'uMvulo', 'uLesibili', 'lesithathu', 'uLesine', 'ngoLesihlanu', 'umGqibelo'], + 'weekdays_short' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], + 'weekdays_min' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nso.php b/libraries/Carbon/src/Carbon/Lang/nso.php new file mode 100644 index 00000000000..c9315b5ca70 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nso.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nso_ZA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/nso_ZA.php b/libraries/Carbon/src/Carbon/Lang/nso_ZA.php new file mode 100644 index 00000000000..8157c58f33d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nso_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janaware', 'Febereware', 'Matšhe', 'Aprele', 'Mei', 'June', 'Julae', 'Agostose', 'Setemere', 'Oktobere', 'Nofemere', 'Disemere'], + 'months_short' => ['Jan', 'Feb', 'Mat', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Set', 'Okt', 'Nof', 'Dis'], + 'weekdays' => ['LaMorena', 'Mošupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Mokibelo'], + 'weekdays_short' => ['Son', 'Moš', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'weekdays_min' => ['Son', 'Moš', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ngwaga', + 'y' => ':count ngwaga', + 'a_year' => ':count ngwaga', + + 'month' => ':count Kgwedi', + 'm' => ':count Kgwedi', + 'a_month' => ':count Kgwedi', + + 'week' => ':count Beke', + 'w' => ':count Beke', + 'a_week' => ':count Beke', + + 'day' => ':count Letšatši', + 'd' => ':count Letšatši', + 'a_day' => ':count Letšatši', + + 'hour' => ':count Iri', + 'h' => ':count Iri', + 'a_hour' => ':count Iri', + + 'minute' => ':count Motsotso', + 'min' => ':count Motsotso', + 'a_minute' => ':count Motsotso', + + 'second' => ':count motsotswana', + 's' => ':count motsotswana', + 'a_second' => ':count motsotswana', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nus.php b/libraries/Carbon/src/Carbon/Lang/nus.php new file mode 100644 index 00000000000..f4ba3322b35 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nus.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['RW', 'TŊ'], + 'weekdays' => ['Cäŋ kuɔth', 'Jiec la̱t', 'Rɛw lätni', 'Diɔ̱k lätni', 'Ŋuaan lätni', 'Dhieec lätni', 'Bäkɛl lätni'], + 'weekdays_short' => ['Cäŋ', 'Jiec', 'Rɛw', 'Diɔ̱k', 'Ŋuaan', 'Dhieec', 'Bäkɛl'], + 'weekdays_min' => ['Cäŋ', 'Jiec', 'Rɛw', 'Diɔ̱k', 'Ŋuaan', 'Dhieec', 'Bäkɛl'], + 'months' => ['Tiop thar pɛt', 'Pɛt', 'Duɔ̱ɔ̱ŋ', 'Guak', 'Duät', 'Kornyoot', 'Pay yie̱tni', 'Tho̱o̱r', 'Tɛɛr', 'Laath', 'Kur', 'Tio̱p in di̱i̱t'], + 'months_short' => ['Tiop', 'Pɛt', 'Duɔ̱ɔ̱', 'Guak', 'Duä', 'Kor', 'Pay', 'Thoo', 'Tɛɛ', 'Laa', 'Kur', 'Tid'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], + + 'year' => ':count jiök', // less reliable + 'y' => ':count jiök', // less reliable + 'a_year' => ':count jiök', // less reliable + + 'month' => ':count pay', // less reliable + 'm' => ':count pay', // less reliable + 'a_month' => ':count pay', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/nyn.php b/libraries/Carbon/src/Carbon/Lang/nyn.php new file mode 100644 index 00000000000..25a2080fd63 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/nyn.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sande', 'Orwokubanza', 'Orwakabiri', 'Orwakashatu', 'Orwakana', 'Orwakataano', 'Orwamukaaga'], + 'weekdays_short' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'weekdays_min' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'months' => ['Okwokubanza', 'Okwakabiri', 'Okwakashatu', 'Okwakana', 'Okwakataana', 'Okwamukaaga', 'Okwamushanju', 'Okwamunaana', 'Okwamwenda', 'Okwaikumi', 'Okwaikumi na kumwe', 'Okwaikumi na ibiri'], + 'months_short' => ['KBZ', 'KBR', 'KST', 'KKN', 'KTN', 'KMK', 'KMS', 'KMN', 'KMW', 'KKM', 'KNK', 'KNB'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/oc.php b/libraries/Carbon/src/Carbon/Lang/oc.php new file mode 100644 index 00000000000..99bde204bb7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/oc.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Quentí + */ +// @codeCoverageIgnoreStart +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(static function ($number) { + return $number == 1 ? 0 : 1; + }, 'oc'); +} +// @codeCoverageIgnoreEnd + +return [ + 'year' => ':count an|:count ans', + 'a_year' => 'un an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count setmana|:count setmanas', + 'a_week' => 'una setmana|:count setmanas', + 'w' => ':count setmana|:count setmanas', + 'day' => ':count jorn|:count jorns', + 'a_day' => 'un jorn|:count jorns', + 'd' => ':count jorn|:count jorns', + 'hour' => ':count ora|:count oras', + 'a_hour' => 'una ora|:count oras', + 'h' => ':count ora|:count oras', + 'minute' => ':count minuta|:count minutas', + 'a_minute' => 'una minuta|:count minutas', + 'min' => ':count minuta|:count minutas', + 'second' => ':count segonda|:count segondas', + 'a_second' => 'una segonda|:count segondas', + 's' => ':count segonda|:count segondas', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquí :time', + 'after' => ':time aprèp', + 'before' => ':time abans', + 'diff_now' => 'ara meteis', + 'diff_today' => 'Uèi', + 'diff_today_regexp' => 'Uèi(?:\\s+a)?', + 'diff_yesterday' => 'ièr', + 'diff_yesterday_regexp' => 'Ièr(?:\\s+a)?', + 'diff_tomorrow' => 'deman', + 'diff_tomorrow_regexp' => 'Deman(?:\\s+a)?', + 'diff_before_yesterday' => 'ièr delà', + 'diff_after_tomorrow' => 'deman passat', + 'period_recurrences' => ':count còp|:count còps', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'fins a :date', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [de] YYYY', + 'LLL' => 'D MMMM [de] YYYY [a] H:mm', + 'LLLL' => 'dddd D MMMM [de] YYYY [a] H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Uèi a] LT', + 'nextDay' => '[Deman a] LT', + 'nextWeek' => 'dddd [a] LT', + 'lastDay' => '[Ièr a] LT', + 'lastWeek' => 'dddd [passat a] LT', + 'sameElse' => 'L', + ], + 'months' => ['de genièr', 'de febrièr', 'de març', 'd\'abrial', 'de mai', 'de junh', 'de julhet', 'd\'agost', 'de setembre', 'd’octòbre', 'de novembre', 'de decembre'], + 'months_standalone' => ['genièr', 'febrièr', 'març', 'abrial', 'mai', 'junh', 'julh', 'agost', 'setembre', 'octòbre', 'novembre', 'decembre'], + 'months_short' => ['gen.', 'feb.', 'març', 'abr.', 'mai', 'junh', 'julh', 'ago.', 'sep.', 'oct.', 'nov.', 'dec.'], + 'weekdays' => ['dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte'], + 'weekdays_short' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], + 'weekdays_min' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], + 'ordinal' => function ($number, string $period = '') { + $ordinal = [1 => 'èr', 2 => 'nd'][(int) $number] ?? 'en'; + + // feminine for year, week, hour, minute, second + if (preg_match('/^[yYwWhHgGis]$/', $period)) { + $ordinal .= 'a'; + } + + return $number.$ordinal; + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/oc_FR.php b/libraries/Carbon/src/Carbon/Lang/oc_FR.php new file mode 100644 index 00000000000..8e2cc9a58d2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/oc_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/oc.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/om.php b/libraries/Carbon/src/Carbon/Lang/om.php new file mode 100644 index 00000000000..3cabe5e38c3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/om.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation & Sagalee Oromoo Publishing Co. Inc. locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'dd-MMM-YYYY', + 'LLL' => 'dd MMMM YYYY HH:mm', + 'LLLL' => 'dddd, MMMM D, YYYY HH:mm', + ], + 'months' => ['Amajjii', 'Guraandhala', 'Bitooteessa', 'Elba', 'Caamsa', 'Waxabajjii', 'Adooleessa', 'Hagayya', 'Fuulbana', 'Onkololeessa', 'Sadaasa', 'Muddee'], + 'months_short' => ['Ama', 'Gur', 'Bit', 'Elb', 'Cam', 'Wax', 'Ado', 'Hag', 'Ful', 'Onk', 'Sad', 'Mud'], + 'weekdays' => ['Dilbata', 'Wiixata', 'Qibxata', 'Roobii', 'Kamiisa', 'Jimaata', 'Sanbata'], + 'weekdays_short' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'weekdays_min' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['WD', 'WB'], + + 'year' => 'wggoota :count', + 'y' => 'wggoota :count', + 'a_year' => 'wggoota :count', + + 'month' => 'ji’a :count', + 'm' => 'ji’a :count', + 'a_month' => 'ji’a :count', + + 'week' => 'torban :count', + 'w' => 'torban :count', + 'a_week' => 'torban :count', + + 'day' => 'guyyaa :count', + 'd' => 'guyyaa :count', + 'a_day' => 'guyyaa :count', + + 'hour' => 'saʼaatii :count', + 'h' => 'saʼaatii :count', + 'a_hour' => 'saʼaatii :count', + + 'minute' => 'daqiiqaa :count', + 'min' => 'daqiiqaa :count', + 'a_minute' => 'daqiiqaa :count', + + 'second' => 'sekoondii :count', + 's' => 'sekoondii :count', + 'a_second' => 'sekoondii :count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/om_ET.php b/libraries/Carbon/src/Carbon/Lang/om_ET.php new file mode 100644 index 00000000000..c19acb405d8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/om_ET.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/om.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/om_KE.php b/libraries/Carbon/src/Carbon/Lang/om_KE.php new file mode 100644 index 00000000000..ac0bcf49eb0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/om_KE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/om.php', [ + 'day_of_first_week_of_year' => 0, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/or.php b/libraries/Carbon/src/Carbon/Lang/or.php new file mode 100644 index 00000000000..4bd08c017ac --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/or.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/or_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/or_IN.php b/libraries/Carbon/src/Carbon/Lang/or_IN.php new file mode 100644 index 00000000000..f634cfb6c31 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/or_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM AP Linux Technology Center, Yamato Software Laboratory bug-glibc@gnu.org + */ +return [ + 'diff_now' => 'ବର୍ତ୍ତମାନ', + 'diff_yesterday' => 'ଗତକାଲି', + 'diff_tomorrow' => 'ଆସନ୍ତାକାଲି', + 'formats' => [ + 'LT' => 'Oh:Om A', + 'LTS' => 'Oh:Om:Os A', + 'L' => 'OD-OM-OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY Oh:Om A', + 'LLLL' => 'dddd OD MMMM OY Oh:Om A', + ], + 'months' => ['ଜାନୁଆରୀ', 'ଫେବୃଆରୀ', 'ମାର୍ଚ୍ଚ', 'ଅପ୍ରେଲ', 'ମଇ', 'ଜୁନ', 'ଜୁଲାଇ', 'ଅଗଷ୍ଟ', 'ସେପ୍ଟେମ୍ବର', 'ଅକ୍ଟୋବର', 'ନଭେମ୍ବର', 'ଡିସେମ୍ବର'], + 'months_short' => ['ଜାନୁଆରୀ', 'ଫେବୃଆରୀ', 'ମାର୍ଚ୍ଚ', 'ଅପ୍ରେଲ', 'ମଇ', 'ଜୁନ', 'ଜୁଲାଇ', 'ଅଗଷ୍ଟ', 'ସେପ୍ଟେମ୍ବର', 'ଅକ୍ଟୋବର', 'ନଭେମ୍ବର', 'ଡିସେମ୍ବର'], + 'weekdays' => ['ରବିବାର', 'ସୋମବାର', 'ମଙ୍ଗଳବାର', 'ବୁଧବାର', 'ଗୁରୁବାର', 'ଶୁକ୍ରବାର', 'ଶନିବାର'], + 'weekdays_short' => ['ରବି', 'ସୋମ', 'ମଙ୍ଗଳ', 'ବୁଧ', 'ଗୁରୁ', 'ଶୁକ୍ର', 'ଶନି'], + 'weekdays_min' => ['ରବି', 'ସୋମ', 'ମଙ୍ଗଳ', 'ବୁଧ', 'ଗୁରୁ', 'ଶୁକ୍ର', 'ଶନି'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯', '୧୦', '୧୧', '୧୨', '୧୩', '୧୪', '୧୫', '୧୬', '୧୭', '୧୮', '୧୯', '୨୦', '୨୧', '୨୨', '୨୩', '୨୪', '୨୫', '୨୬', '୨୭', '୨୮', '୨୯', '୩୦', '୩୧', '୩୨', '୩୩', '୩୪', '୩୫', '୩୬', '୩୭', '୩୮', '୩୯', '୪୦', '୪୧', '୪୨', '୪୩', '୪୪', '୪୫', '୪୬', '୪୭', '୪୮', '୪୯', '୫୦', '୫୧', '୫୨', '୫୩', '୫୪', '୫୫', '୫୬', '୫୭', '୫୮', '୫୯', '୬୦', '୬୧', '୬୨', '୬୩', '୬୪', '୬୫', '୬୬', '୬୭', '୬୮', '୬୯', '୭୦', '୭୧', '୭୨', '୭୩', '୭୪', '୭୫', '୭୬', '୭୭', '୭୮', '୭୯', '୮୦', '୮୧', '୮୨', '୮୩', '୮୪', '୮୫', '୮୬', '୮୭', '୮୮', '୮୯', '୯୦', '୯୧', '୯୨', '୯୩', '୯୪', '୯୫', '୯୬', '୯୭', '୯୮', '୯୯'], + 'year' => ':count ବର୍ଷ', + 'y' => ':count ବ.', + 'month' => ':count ମାସ', + 'm' => ':count ମା.', + 'week' => ':count ସପ୍ତାହ', + 'w' => ':count ସପ୍ତା.', + 'day' => ':count ଦିନ', + 'd' => ':count ଦିନ', + 'hour' => ':count ଘଣ୍ତ', + 'h' => ':count ଘ.', + 'minute' => ':count ମିନଟ', + 'min' => ':count ମି.', + 'second' => ':count ସେକଣ୍ଢ', + 's' => ':count ସେ.', + 'ago' => ':time ପୂର୍ବେ', + 'from_now' => ':timeରେ', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/os.php b/libraries/Carbon/src/Carbon/Lang/os.php new file mode 100644 index 00000000000..9907f63fc7b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/os.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/os_RU.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/os_RU.php b/libraries/Carbon/src/Carbon/Lang/os_RU.php new file mode 100644 index 00000000000..aa63edb3715 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/os_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['январы', 'февралы', 'мартъийы', 'апрелы', 'майы', 'июны', 'июлы', 'августы', 'сентябры', 'октябры', 'ноябры', 'декабры'], + 'months_short' => ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'], + 'weekdays' => ['Хуыцаубон', 'Къуырисæр', 'Дыццæг', 'Æртыццæг', 'Цыппæрæм', 'Майрæмбон', 'Сабат'], + 'weekdays_short' => ['Хцб', 'Крс', 'Дцг', 'Æрт', 'Цпр', 'Мрб', 'Сбт'], + 'weekdays_min' => ['Хцб', 'Крс', 'Дцг', 'Æрт', 'Цпр', 'Мрб', 'Сбт'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count гыццыл', // less reliable + 'min' => ':count гыццыл', // less reliable + 'a_minute' => ':count гыццыл', // less reliable + + 'second' => ':count æндæр', // less reliable + 's' => ':count æндæр', // less reliable + 'a_second' => ':count æндæр', // less reliable + + 'year' => ':count аз', + 'y' => ':count аз', + 'a_year' => ':count аз', + + 'month' => ':count мӕй', + 'm' => ':count мӕй', + 'a_month' => ':count мӕй', + + 'week' => ':count къуыри', + 'w' => ':count къуыри', + 'a_week' => ':count къуыри', + + 'day' => ':count бон', + 'd' => ':count бон', + 'a_day' => ':count бон', + + 'hour' => ':count сахат', + 'h' => ':count сахат', + 'a_hour' => ':count сахат', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/pa.php b/libraries/Carbon/src/Carbon/Lang/pa.php new file mode 100644 index 00000000000..9dfd9b41655 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pa.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - Punjab + */ +return [ + 'year' => 'ਇੱਕ ਸਾਲ|:count ਸਾਲ', + 'month' => 'ਇੱਕ ਮਹੀਨਾ|:count ਮਹੀਨੇ', + 'week' => 'ਹਫਤਾ|:count ਹਫ਼ਤੇ', + 'day' => 'ਇੱਕ ਦਿਨ|:count ਦਿਨ', + 'hour' => 'ਇੱਕ ਘੰਟਾ|:count ਘੰਟੇ', + 'minute' => 'ਇਕ ਮਿੰਟ|:count ਮਿੰਟ', + 'second' => 'ਕੁਝ ਸਕਿੰਟ|:count ਸਕਿੰਟ', + 'ago' => ':time ਪਹਿਲਾਂ', + 'from_now' => ':time ਵਿੱਚ', + 'before' => ':time ਤੋਂ ਪਹਿਲਾਂ', + 'after' => ':time ਤੋਂ ਬਾਅਦ', + 'diff_now' => 'ਹੁਣ', + 'diff_today' => 'ਅਜ', + 'diff_yesterday' => 'ਕਲ', + 'diff_tomorrow' => 'ਕਲ', + 'formats' => [ + 'LT' => 'A h:mm ਵਜੇ', + 'LTS' => 'A h:mm:ss ਵਜੇ', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm ਵਜੇ', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm ਵਜੇ', + ], + 'calendar' => [ + 'sameDay' => '[ਅਜ] LT', + 'nextDay' => '[ਕਲ] LT', + 'nextWeek' => '[ਅਗਲਾ] dddd, LT', + 'lastDay' => '[ਕਲ] LT', + 'lastWeek' => '[ਪਿਛਲੇ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ਰਾਤ'; + } + if ($hour < 10) { + return 'ਸਵੇਰ'; + } + if ($hour < 17) { + return 'ਦੁਪਹਿਰ'; + } + if ($hour < 20) { + return 'ਸ਼ਾਮ'; + } + + return 'ਰਾਤ'; + }, + 'months' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'months_short' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'weekdays' => ['ਐਤਵਾਰ', 'ਸੋਮਵਾਰ', 'ਮੰਗਲਵਾਰ', 'ਬੁਧਵਾਰ', 'ਵੀਰਵਾਰ', 'ਸ਼ੁੱਕਰਵਾਰ', 'ਸ਼ਨੀਚਰਵਾਰ'], + 'weekdays_short' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁਧ', 'ਵੀਰ', 'ਸ਼ੁਕਰ', 'ਸ਼ਨੀ'], + 'weekdays_min' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁਧ', 'ਵੀਰ', 'ਸ਼ੁਕਰ', 'ਸ਼ਨੀ'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ਅਤੇ '], + 'weekend' => [0, 0], + 'alt_numbers' => ['੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/pa_Arab.php b/libraries/Carbon/src/Carbon/Lang/pa_Arab.php new file mode 100644 index 00000000000..54d4c7b4d4e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pa_Arab.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'weekdays' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'weekdays_short' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'weekdays_min' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئ', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئ', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, DD MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/pa_Guru.php b/libraries/Carbon/src/Carbon/Lang/pa_Guru.php new file mode 100644 index 00000000000..fc771f5dcb3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pa_Guru.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pa.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', + ], + 'months' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'months_short' => ['ਜਨ', 'ਫ਼ਰ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾ', 'ਅਗ', 'ਸਤੰ', 'ਅਕਤੂ', 'ਨਵੰ', 'ਦਸੰ'], + 'weekdays' => ['ਐਤਵਾਰ', 'ਸੋਮਵਾਰ', 'ਮੰਗਲਵਾਰ', 'ਬੁੱਧਵਾਰ', 'ਵੀਰਵਾਰ', 'ਸ਼ੁੱਕਰਵਾਰ', 'ਸ਼ਨਿੱਚਰਵਾਰ'], + 'weekdays_short' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁੱਧ', 'ਵੀਰ', 'ਸ਼ੁੱਕਰ', 'ਸ਼ਨਿੱਚਰ'], + 'weekdays_min' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗ', 'ਬੁੱਧ', 'ਵੀਰ', 'ਸ਼ੁੱਕ', 'ਸ਼ਨਿੱ'], + 'weekend' => [0, 0], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/pa_IN.php b/libraries/Carbon/src/Carbon/Lang/pa_IN.php new file mode 100644 index 00000000000..fe84a04f716 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pa_IN.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Guo Xiang Tan + * - Josh Soref + * - Ash + * - harpreetkhalsagtbit + */ +return require __DIR__.'/pa.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pa_PK.php b/libraries/Carbon/src/Carbon/Lang/pa_PK.php new file mode 100644 index 00000000000..e5c8abdfe3e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pa_PK.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['جنوري', 'فروري', 'مارچ', 'اپريل', 'مٓی', 'جون', 'جولاي', 'اگست', 'ستمبر', 'اكتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوري', 'فروري', 'مارچ', 'اپريل', 'مٓی', 'جون', 'جولاي', 'اگست', 'ستمبر', 'اكتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_short' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_min' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ص', 'ش'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/pap.php b/libraries/Carbon/src/Carbon/Lang/pap.php new file mode 100644 index 00000000000..5a28460d0cd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pap.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return [ + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm:ss', + 'L' => 'DD-MM-YY', + 'LL' => 'MMMM [di] DD, YYYY', + 'LLL' => 'DD MMM HH.mm', + 'LLLL' => 'MMMM DD, YYYY HH.mm', + ], + 'months' => ['yanüari', 'febrüari', 'mart', 'aprel', 'mei', 'yüni', 'yüli', 'ougùstùs', 'sèptèmber', 'oktober', 'novèmber', 'desèmber'], + 'months_short' => ['yan', 'feb', 'mar', 'apr', 'mei', 'yün', 'yül', 'oug', 'sèp', 'okt', 'nov', 'des'], + 'weekdays' => ['djadomingo', 'djaluna', 'djamars', 'djawebs', 'djarason', 'djabierne', 'djasabra'], + 'weekdays_short' => ['do', 'lu', 'ma', 'we', 'ra', 'bi', 'sa'], + 'weekdays_min' => ['do', 'lu', 'ma', 'we', 'ra', 'bi', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count aña', + 'month' => ':count luna', + 'week' => ':count siman', + 'day' => ':count dia', + 'hour' => ':count ora', + 'minute' => ':count minüt', + 'second' => ':count sekònde', + 'list' => [', ', ' i '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/pap_AW.php b/libraries/Carbon/src/Carbon/Lang/pap_AW.php new file mode 100644 index 00000000000..154519be4a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pap_AW.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from native speaker Pablo Saratxaga pablo@mandrakesoft.com + */ +return require __DIR__.'/pap.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pap_CW.php b/libraries/Carbon/src/Carbon/Lang/pap_CW.php new file mode 100644 index 00000000000..154519be4a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pap_CW.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from native speaker Pablo Saratxaga pablo@mandrakesoft.com + */ +return require __DIR__.'/pap.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pl.php b/libraries/Carbon/src/Carbon/Lang/pl.php new file mode 100644 index 00000000000..efa6b8233fd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pl.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Wacław Jacek + * - François B + * - Tim Fish + * - Serhan Apaydın + * - Massimiliano Caniparoli + * - JD Isaacks + * - Jakub Szwacz + * - Jan + * - Paul + * - damlys + * - Marek (marast78) + * - Peter (UnrulyNatives) + * - Qrzysio + * - Jan (aso824) + * - diverpl + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count rok|:count lata|:count lat', + 'a_year' => 'rok|:count lata|:count lat', + 'y' => ':count r|:count l|:count l', + 'month' => ':count miesiąc|:count miesiące|:count miesięcy', + 'a_month' => 'miesiąc|:count miesiące|:count miesięcy', + 'm' => ':count mies.', + 'week' => ':count tydzień|:count tygodnie|:count tygodni', + 'a_week' => 'tydzień|:count tygodnie|:count tygodni', + 'w' => ':count tyg.', + 'day' => ':count dzień|:count dni|:count dni', + 'a_day' => 'dzień|:count dni|:count dni', + 'd' => ':count d', + 'hour' => ':count godzina|:count godziny|:count godzin', + 'a_hour' => 'godzina|:count godziny|:count godzin', + 'h' => ':count godz.', + 'minute' => ':count minuta|:count minuty|:count minut', + 'a_minute' => 'minuta|:count minuty|:count minut', + 'min' => ':count min', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 'a_second' => '{1}kilka sekund|:count sekunda|:count sekundy|:count sekund', + 's' => ':count sek.', + 'ago' => ':time temu', + 'from_now' => static function ($time) { + return 'za '.strtr($time, [ + 'godzina' => 'godzinę', + 'minuta' => 'minutę', + 'sekunda' => 'sekundę', + ]); + }, + 'after' => ':time po', + 'before' => ':time przed', + 'diff_now' => 'teraz', + 'diff_today' => 'Dziś', + 'diff_today_regexp' => 'Dziś(?:\\s+o)?', + 'diff_yesterday' => 'wczoraj', + 'diff_yesterday_regexp' => 'Wczoraj(?:\\s+o)?', + 'diff_tomorrow' => 'jutro', + 'diff_tomorrow_regexp' => 'Jutro(?:\\s+o)?', + 'diff_before_yesterday' => 'przedwczoraj', + 'diff_after_tomorrow' => 'pojutrze', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Dziś o] LT', + 'nextDay' => '[Jutro o] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[W niedzielę o] LT'; + case 2: + return '[We wtorek o] LT'; + case 3: + return '[W środę o] LT'; + case 6: + return '[W sobotę o] LT'; + default: + return '[W] dddd [o] LT'; + } + }, + 'lastDay' => '[Wczoraj o] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[W zeszłą niedzielę o] LT'; + case 3: + return '[W zeszłą środę o] LT'; + case 6: + return '[W zeszłą sobotę o] LT'; + default: + return '[W zeszły] dddd [o] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['stycznia', 'lutego', 'marca', 'kwietnia', 'maja', 'czerwca', 'lipca', 'sierpnia', 'września', 'października', 'listopada', 'grudnia'], + 'months_standalone' => ['styczeń', 'luty', 'marzec', 'kwiecień', 'maj', 'czerwiec', 'lipiec', 'sierpień', 'wrzesień', 'październik', 'listopad', 'grudzień'], + 'months_short' => ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['niedziela', 'poniedziałek', 'wtorek', 'środa', 'czwartek', 'piątek', 'sobota'], + 'weekdays_short' => ['ndz', 'pon', 'wt', 'śr', 'czw', 'pt', 'sob'], + 'weekdays_min' => ['Nd', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' i '], + 'meridiem' => ['przed południem', 'po południu'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/pl_PL.php b/libraries/Carbon/src/Carbon/Lang/pl_PL.php new file mode 100644 index 00000000000..c12bfd2193a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pl_PL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/prg.php b/libraries/Carbon/src/Carbon/Lang/prg.php new file mode 100644 index 00000000000..b9b4300499c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/prg.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count meta', + 'y' => ':count meta', + 'a_year' => ':count meta', + + 'month' => ':count mēniks', // less reliable + 'm' => ':count mēniks', // less reliable + 'a_month' => ':count mēniks', // less reliable + + 'week' => ':count sawaītin', // less reliable + 'w' => ':count sawaītin', // less reliable + 'a_week' => ':count sawaītin', // less reliable + + 'day' => ':count di', + 'd' => ':count di', + 'a_day' => ':count di', + + 'hour' => ':count bruktēt', // less reliable + 'h' => ':count bruktēt', // less reliable + 'a_hour' => ':count bruktēt', // less reliable + + 'minute' => ':count līkuts', // less reliable + 'min' => ':count līkuts', // less reliable + 'a_minute' => ':count līkuts', // less reliable + + 'second' => ':count kitan', // less reliable + 's' => ':count kitan', // less reliable + 'a_second' => ':count kitan', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ps.php b/libraries/Carbon/src/Carbon/Lang/ps.php new file mode 100644 index 00000000000..6a805d97c94 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ps.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Muhammad Nasir Rahimi + * - Nassim Nasibullah (spinzar) + */ +return [ + 'year' => ':count کال|:count کاله', + 'y' => ':countکال|:countکاله', + 'month' => ':count مياشت|:count مياشتي', + 'm' => ':countمياشت|:countمياشتي', + 'week' => ':count اونۍ|:count اونۍ', + 'w' => ':countاونۍ|:countاونۍ', + 'day' => ':count ورځ|:count ورځي', + 'd' => ':countورځ|:countورځي', + 'hour' => ':count ساعت|:count ساعته', + 'h' => ':countساعت|:countساعته', + 'minute' => ':count دقيقه|:count دقيقې', + 'min' => ':countدقيقه|:countدقيقې', + 'second' => ':count ثانيه|:count ثانيې', + 's' => ':countثانيه|:countثانيې', + 'ago' => ':time دمخه', + 'from_now' => ':time له اوس څخه', + 'after' => ':time وروسته', + 'before' => ':time دمخه', + 'list' => ['، ', ' او '], + 'meridiem' => ['غ.م.', 'غ.و.'], + 'weekdays' => ['اتوار', 'ګل', 'نهه', 'شورو', 'زيارت', 'جمعه', 'خالي'], + 'weekdays_short' => ['ا', 'ګ', 'ن', 'ش', 'ز', 'ج', 'خ'], + 'weekdays_min' => ['ا', 'ګ', 'ن', 'ش', 'ز', 'ج', 'خ'], + 'months' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سېپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سېپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_standalone' => ['جنوري', 'فېبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short_standalone' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'first_day_of_week' => 6, + 'weekend' => [4, 5], + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'YYYY/M/d', + 'LL' => 'YYYY MMM D', + 'LLL' => 'د YYYY د MMMM D H:mm', + 'LLLL' => 'dddd د YYYY د MMMM D H:mm', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ps_AF.php b/libraries/Carbon/src/Carbon/Lang/ps_AF.php new file mode 100644 index 00000000000..456d7416925 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ps_AF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ps.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pt.php b/libraries/Carbon/src/Carbon/Lang/pt.php new file mode 100644 index 00000000000..f0624d4721b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cassiano Montanari + * - Matt Pope + * - François B + * - Prodis + * - JD Isaacks + * - Raphael Amorim + * - João Magalhães + * - victortobias + * - Paulo Freitas + * - Sebastian Thierer + * - Claudson Martins (claudsonm) + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count ano|:count anos', + 'a_year' => 'um ano|:count anos', + 'y' => ':counta', + 'month' => ':count mês|:count meses', + 'a_month' => 'um mês|:count meses', + 'm' => ':countm', + 'week' => ':count semana|:count semanas', + 'a_week' => 'uma semana|:count semanas', + 'w' => ':countsem', + 'day' => ':count dia|:count dias', + 'a_day' => 'um dia|:count dias', + 'd' => ':countd', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'uma hora|:count horas', + 'h' => ':counth', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'um minuto|:count minutos', + 'min' => ':countmin', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'alguns segundos|:count segundos', + 's' => ':counts', + 'millisecond' => ':count milissegundo|:count milissegundos', + 'a_millisecond' => 'um milissegundo|:count milissegundos', + 'ms' => ':countms', + 'microsecond' => ':count microssegundo|:count microssegundos', + 'a_microsecond' => 'um microssegundo|:count microssegundos', + 'µs' => ':countµs', + 'ago' => 'há :time', + 'from_now' => 'em :time', + 'after' => ':time depois', + 'before' => ':time antes', + 'diff_now' => 'agora', + 'diff_today' => 'Hoje', + 'diff_today_regexp' => 'Hoje(?:\\s+às)?', + 'diff_yesterday' => 'ontem', + 'diff_yesterday_regexp' => 'Ontem(?:\\s+às)?', + 'diff_tomorrow' => 'amanhã', + 'diff_tomorrow_regexp' => 'Amanhã(?:\\s+às)?', + 'diff_before_yesterday' => 'anteontem', + 'diff_after_tomorrow' => 'depois de amanhã', + 'period_recurrences' => 'uma vez|:count vezes', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'até :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hoje às] LT', + 'nextDay' => '[Amanhã às] LT', + 'nextWeek' => 'dddd [às] LT', + 'lastDay' => '[Ontem às] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 6: + return '[Último] dddd [às] LT'; + default: + return '[Última] dddd [às] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], + 'months_short' => ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], + 'weekdays' => ['domingo', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sábado'], + 'weekdays_short' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'weekdays_min' => ['Do', '2ª', '3ª', '4ª', '5ª', '6ª', 'Sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'primeira', + 'second' => 'segunda', + 'third' => 'terceira', + 'fourth' => 'quarta', + 'fifth' => 'quinta', + 'last' => 'última', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/pt_AO.php b/libraries/Carbon/src/Carbon/Lang/pt_AO.php new file mode 100644 index 00000000000..4027d896f3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_AO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pt_BR.php b/libraries/Carbon/src/Carbon/Lang/pt_BR.php new file mode 100644 index 00000000000..eeccee8fb1d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_BR.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cassiano Montanari + * - Eduardo Dalla Vecchia + * - David Rodrigues + * - Matt Pope + * - François B + * - Prodis + * - Marlon Maxwel + * - JD Isaacks + * - Raphael Amorim + * - Rafael Raupp + * - felipeleite1 + * - swalker + * - Lucas Macedo + * - Paulo Freitas + * - Sebastian Thierer + */ +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'period_recurrences' => 'uma|:count vez', + 'period_interval' => 'toda :interval', + 'formats' => [ + 'LLL' => 'D [de] MMMM [de] YYYY [às] HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY [às] HH:mm', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/pt_CH.php b/libraries/Carbon/src/Carbon/Lang/pt_CH.php new file mode 100644 index 00000000000..4027d896f3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_CH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pt_CV.php b/libraries/Carbon/src/Carbon/Lang/pt_CV.php new file mode 100644 index 00000000000..4027d896f3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_CV.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pt_GQ.php b/libraries/Carbon/src/Carbon/Lang/pt_GQ.php new file mode 100644 index 00000000000..4027d896f3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_GQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pt_GW.php b/libraries/Carbon/src/Carbon/Lang/pt_GW.php new file mode 100644 index 00000000000..4027d896f3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_GW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pt_LU.php b/libraries/Carbon/src/Carbon/Lang/pt_LU.php new file mode 100644 index 00000000000..4027d896f3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_LU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pt_MO.php b/libraries/Carbon/src/Carbon/Lang/pt_MO.php new file mode 100644 index 00000000000..e24db30ac06 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_MO.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'LLL' => 'D [de] MMMM [de] YYYY, h:mm a', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY, h:mm a', + ], + 'first_day_of_week' => 0, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/pt_MZ.php b/libraries/Carbon/src/Carbon/Lang/pt_MZ.php new file mode 100644 index 00000000000..8963e80f68b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_MZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'first_day_of_week' => 0, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/pt_PT.php b/libraries/Carbon/src/Carbon/Lang/pt_PT.php new file mode 100644 index 00000000000..ff7b9cf44ec --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_PT.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], + 'months_short' => ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], + 'weekdays' => ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'], + 'weekdays_short' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'weekdays_min' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/pt_ST.php b/libraries/Carbon/src/Carbon/Lang/pt_ST.php new file mode 100644 index 00000000000..4027d896f3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_ST.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/pt_TL.php b/libraries/Carbon/src/Carbon/Lang/pt_TL.php new file mode 100644 index 00000000000..4027d896f3a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/pt_TL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/qu.php b/libraries/Carbon/src/Carbon/Lang/qu.php new file mode 100644 index 00000000000..6cf77c72f09 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/qu.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es_UY.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM, YYYY HH:mm', + ], + 'first_day_of_week' => 0, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/qu_BO.php b/libraries/Carbon/src/Carbon/Lang/qu_BO.php new file mode 100644 index 00000000000..09b4dce4728 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/qu_BO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/qu.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/qu_EC.php b/libraries/Carbon/src/Carbon/Lang/qu_EC.php new file mode 100644 index 00000000000..09b4dce4728 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/qu_EC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/qu.php', [ + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/quz.php b/libraries/Carbon/src/Carbon/Lang/quz.php new file mode 100644 index 00000000000..baedc42a650 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/quz.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/quz_PE.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/quz_PE.php b/libraries/Carbon/src/Carbon/Lang/quz_PE.php new file mode 100644 index 00000000000..2aba9397395 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/quz_PE.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['iniru', 'phiwriru', 'marsu', 'awril', 'mayu', 'huniyu', 'huliyu', 'agustu', 'siptiyimri', 'uktuwri', 'nuwiyimri', 'tisiyimri'], + 'months_short' => ['ini', 'phi', 'mar', 'awr', 'may', 'hun', 'hul', 'agu', 'sip', 'ukt', 'nuw', 'tis'], + 'weekdays' => ['tuminku', 'lunis', 'martis', 'miyirkulis', 'juywis', 'wiyirnis', 'sawatu'], + 'weekdays_short' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'weekdays_min' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count uchuy', // less reliable + 'min' => ':count uchuy', // less reliable + 'a_minute' => ':count uchuy', // less reliable + + 'year' => ':count wata', + 'y' => ':count wata', + 'a_year' => ':count wata', + + 'month' => ':count killa', + 'm' => ':count killa', + 'a_month' => ':count killa', + + 'week' => ':count simana', + 'w' => ':count simana', + 'a_week' => ':count simana', + + 'day' => ':count pʼunchaw', + 'd' => ':count pʼunchaw', + 'a_day' => ':count pʼunchaw', + + 'hour' => ':count ura', + 'h' => ':count ura', + 'a_hour' => ':count ura', + + 'second' => ':count iskay ñiqin', + 's' => ':count iskay ñiqin', + 'a_second' => ':count iskay ñiqin', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/raj.php b/libraries/Carbon/src/Carbon/Lang/raj.php new file mode 100644 index 00000000000..0d1aac0c09f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/raj.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/raj_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/raj_IN.php b/libraries/Carbon/src/Carbon/Lang/raj_IN.php new file mode 100644 index 00000000000..99f909079e2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/raj_IN.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - meghrajsuthar03@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर'], + 'months_short' => ['जन', 'फर', 'मार्च', 'अप्रै', 'मई', 'जून', 'जुल', 'अग', 'सित', 'अक्टू', 'नव', 'दिस'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगल्लवार', 'बुधवार', 'बृहस्पतिवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'year' => ':count आंहू', // less reliable + 'y' => ':count आंहू', // less reliable + 'a_year' => ':count आंहू', // less reliable + + 'month' => ':count सूरज', // less reliable + 'm' => ':count सूरज', // less reliable + 'a_month' => ':count सूरज', // less reliable + + 'week' => ':count निवाज', // less reliable + 'w' => ':count निवाज', // less reliable + 'a_week' => ':count निवाज', // less reliable + + 'day' => ':count अेक', // less reliable + 'd' => ':count अेक', // less reliable + 'a_day' => ':count अेक', // less reliable + + 'hour' => ':count दुनियांण', // less reliable + 'h' => ':count दुनियांण', // less reliable + 'a_hour' => ':count दुनियांण', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/rm.php b/libraries/Carbon/src/Carbon/Lang/rm.php new file mode 100644 index 00000000000..68b10049b59 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/rm.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Tsutomu Kuroda + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - sebastian de castelberg + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'Do MMMM YYYY', + 'LLL' => 'Do MMMM, HH:mm [Uhr]', + 'LLLL' => 'dddd, Do MMMM YYYY, HH:mm [Uhr]', + ], + 'year' => ':count onn|:count onns', + 'month' => ':count mais', + 'week' => ':count emna|:count emnas', + 'day' => ':count di|:count dis', + 'hour' => ':count oura|:count ouras', + 'minute' => ':count minuta|:count minutas', + 'second' => ':count secunda|:count secundas', + 'weekdays' => ['dumengia', 'glindesdi', 'mardi', 'mesemna', 'gievgia', 'venderdi', 'sonda'], + 'weekdays_short' => ['du', 'gli', 'ma', 'me', 'gie', 've', 'so'], + 'weekdays_min' => ['du', 'gli', 'ma', 'me', 'gie', 've', 'so'], + 'months' => ['schaner', 'favrer', 'mars', 'avrigl', 'matg', 'zercladur', 'fanadur', 'avust', 'settember', 'october', 'november', 'december'], + 'months_short' => ['schan', 'favr', 'mars', 'avr', 'matg', 'zercl', 'fan', 'avust', 'sett', 'oct', 'nov', 'dec'], + 'meridiem' => ['avantmezdi', 'suentermezdi'], + 'list' => [', ', ' e '], + 'first_day_of_week' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/rn.php b/libraries/Carbon/src/Carbon/Lang/rn.php new file mode 100644 index 00000000000..a715ff69ffd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/rn.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Z.MU.', 'Z.MW.'], + 'weekdays' => ['Ku w’indwi', 'Ku wa mbere', 'Ku wa kabiri', 'Ku wa gatatu', 'Ku wa kane', 'Ku wa gatanu', 'Ku wa gatandatu'], + 'weekdays_short' => ['cu.', 'mbe.', 'kab.', 'gtu.', 'kan.', 'gnu.', 'gnd.'], + 'weekdays_min' => ['cu.', 'mbe.', 'kab.', 'gtu.', 'kan.', 'gnu.', 'gnd.'], + 'months' => ['Nzero', 'Ruhuhuma', 'Ntwarante', 'Ndamukiza', 'Rusama', 'Ruheshi', 'Mukakaro', 'Nyandagaro', 'Nyakanga', 'Gitugutu', 'Munyonyo', 'Kigarama'], + 'months_short' => ['Mut.', 'Gas.', 'Wer.', 'Mat.', 'Gic.', 'Kam.', 'Nya.', 'Kan.', 'Nze.', 'Ukw.', 'Ugu.', 'Uku.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => 'imyaka :count', + 'y' => 'imyaka :count', + 'a_year' => 'imyaka :count', + + 'month' => 'amezi :count', + 'm' => 'amezi :count', + 'a_month' => 'amezi :count', + + 'week' => 'indwi :count', + 'w' => 'indwi :count', + 'a_week' => 'indwi :count', + + 'day' => 'imisi :count', + 'd' => 'imisi :count', + 'a_day' => 'imisi :count', + + 'hour' => 'amasaha :count', + 'h' => 'amasaha :count', + 'a_hour' => 'amasaha :count', + + 'minute' => 'iminuta :count', + 'min' => 'iminuta :count', + 'a_minute' => 'iminuta :count', + + 'second' => 'inguvu :count', // less reliable + 's' => 'inguvu :count', // less reliable + 'a_second' => 'inguvu :count', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ro.php b/libraries/Carbon/src/Carbon/Lang/ro.php new file mode 100644 index 00000000000..9844837ebde --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ro.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + * - Cătălin Georgescu + * - Valentin Ivaşcu (oriceon) + */ +return [ + 'year' => ':count an|:count ani|:count ani', + 'a_year' => 'un an|:count ani|:count ani', + 'y' => ':count a.', + 'month' => ':count lună|:count luni|:count luni', + 'a_month' => 'o lună|:count luni|:count luni', + 'm' => ':count l.', + 'week' => ':count săptămână|:count săptămâni|:count săptămâni', + 'a_week' => 'o săptămână|:count săptămâni|:count săptămâni', + 'w' => ':count săp.', + 'day' => ':count zi|:count zile|:count zile', + 'a_day' => 'o zi|:count zile|:count zile', + 'd' => ':count z.', + 'hour' => ':count oră|:count ore|:count ore', + 'a_hour' => 'o oră|:count ore|:count ore', + 'h' => ':count o.', + 'minute' => ':count minut|:count minute|:count minute', + 'a_minute' => 'un minut|:count minute|:count minute', + 'min' => ':count m.', + 'second' => ':count secundă|:count secunde|:count secunde', + 'a_second' => 'câteva secunde|:count secunde|:count secunde', + 's' => ':count sec.', + 'ago' => ':time în urmă', + 'from_now' => 'peste :time', + 'after' => 'peste :time', + 'before' => 'acum :time', + 'diff_now' => 'acum', + 'diff_today' => 'azi', + 'diff_today_regexp' => 'azi(?:\\s+la)?', + 'diff_yesterday' => 'ieri', + 'diff_yesterday_regexp' => 'ieri(?:\\s+la)?', + 'diff_tomorrow' => 'mâine', + 'diff_tomorrow_regexp' => 'mâine(?:\\s+la)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[azi la] LT', + 'nextDay' => '[mâine la] LT', + 'nextWeek' => 'dddd [la] LT', + 'lastDay' => '[ieri la] LT', + 'lastWeek' => '[fosta] dddd [la] LT', + 'sameElse' => 'L', + ], + 'months' => ['ianuarie', 'februarie', 'martie', 'aprilie', 'mai', 'iunie', 'iulie', 'august', 'septembrie', 'octombrie', 'noiembrie', 'decembrie'], + 'months_short' => ['ian.', 'feb.', 'mar.', 'apr.', 'mai', 'iun.', 'iul.', 'aug.', 'sept.', 'oct.', 'nov.', 'dec.'], + 'weekdays' => ['duminică', 'luni', 'marți', 'miercuri', 'joi', 'vineri', 'sâmbătă'], + 'weekdays_short' => ['dum', 'lun', 'mar', 'mie', 'joi', 'vin', 'sâm'], + 'weekdays_min' => ['du', 'lu', 'ma', 'mi', 'jo', 'vi', 'sâ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' și '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ro_MD.php b/libraries/Carbon/src/Carbon/Lang/ro_MD.php new file mode 100644 index 00000000000..3c2a9547b37 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ro_MD.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ro.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ro_RO.php b/libraries/Carbon/src/Carbon/Lang/ro_RO.php new file mode 100644 index 00000000000..4198a5d9fa4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ro_RO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ro.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/rof.php b/libraries/Carbon/src/Carbon/Lang/rof.php new file mode 100644 index 00000000000..5a87141f76e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/rof.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kang’ama', 'kingoto'], + 'weekdays' => ['Ijumapili', 'Ijumatatu', 'Ijumanne', 'Ijumatano', 'Alhamisi', 'Ijumaa', 'Ijumamosi'], + 'weekdays_short' => ['Ijp', 'Ijt', 'Ijn', 'Ijtn', 'Alh', 'Iju', 'Ijm'], + 'weekdays_min' => ['Ijp', 'Ijt', 'Ijn', 'Ijtn', 'Alh', 'Iju', 'Ijm'], + 'months' => ['Mweri wa kwanza', 'Mweri wa kaili', 'Mweri wa katatu', 'Mweri wa kaana', 'Mweri wa tanu', 'Mweri wa sita', 'Mweri wa saba', 'Mweri wa nane', 'Mweri wa tisa', 'Mweri wa ikumi', 'Mweri wa ikumi na moja', 'Mweri wa ikumi na mbili'], + 'months_short' => ['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ru.php b/libraries/Carbon/src/Carbon/Lang/ru.php new file mode 100644 index 00000000000..507e43be57f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ru.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Bari Badamshin + * - Jørn Ølmheim + * - François B + * - Tim Fish + * - Коренберг Марк (imac) + * - Serhan Apaydın + * - RomeroMsk + * - vsn4ik + * - JD Isaacks + * - Bari Badamshin + * - Jørn Ølmheim + * - François B + * - Коренберг Марк (imac) + * - Serhan Apaydın + * - RomeroMsk + * - vsn4ik + * - JD Isaacks + * - Fellzo + * - andrey-helldar + * - Pavel Skripkin (psxx) + * - AlexWalkerson + * - Vladislav UnsealedOne + * - dima-bzz + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +$transformDiff = function ($input) { + return strtr($input, [ + 'неделя' => 'неделю', + 'секунда' => 'секунду', + 'минута' => 'минуту', + ]); +}; + +return [ + 'year' => ':count год|:count года|:count лет', + 'y' => ':count г.|:count г.|:count л.', + 'a_year' => '{1}год|:count год|:count года|:count лет', + 'month' => ':count месяц|:count месяца|:count месяцев', + 'm' => ':count мес.', + 'a_month' => '{1}месяц|:count месяц|:count месяца|:count месяцев', + 'week' => ':count неделя|:count недели|:count недель', + 'w' => ':count нед.', + 'a_week' => '{1}неделя|:count неделю|:count недели|:count недель', + 'day' => ':count день|:count дня|:count дней', + 'd' => ':count д.', + 'a_day' => '{1}день|:count день|:count дня|:count дней', + 'hour' => ':count час|:count часа|:count часов', + 'h' => ':count ч.', + 'a_hour' => '{1}час|:count час|:count часа|:count часов', + 'minute' => ':count минута|:count минуты|:count минут', + 'min' => ':count мин.', + 'a_minute' => '{1}минута|:count минута|:count минуты|:count минут', + 'second' => ':count секунда|:count секунды|:count секунд', + 's' => ':count сек.', + 'a_second' => '{1}несколько секунд|:count секунду|:count секунды|:count секунд', + 'ago' => function ($time) use ($transformDiff) { + return $transformDiff($time).' назад'; + }, + 'from_now' => function ($time) use ($transformDiff) { + return 'через '.$transformDiff($time); + }, + 'after' => function ($time) use ($transformDiff) { + return $transformDiff($time).' после'; + }, + 'before' => function ($time) use ($transformDiff) { + return $transformDiff($time).' до'; + }, + 'diff_now' => 'только что', + 'diff_today' => 'Сегодня,', + 'diff_today_regexp' => 'Сегодня,?(?:\\s+в)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера,?(?:\\s+в)?', + 'diff_tomorrow' => 'завтра', + 'diff_tomorrow_regexp' => 'Завтра,?(?:\\s+в)?', + 'diff_before_yesterday' => 'позавчера', + 'diff_after_tomorrow' => 'послезавтра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY г.', + 'LLL' => 'D MMMM YYYY г., H:mm', + 'LLLL' => 'dddd, D MMMM YYYY г., H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Сегодня, в] LT', + 'nextDay' => '[Завтра, в] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($current->week !== $other->week) { + switch ($current->dayOfWeek) { + case 0: + return '[В следующее] dddd, [в] LT'; + case 1: + case 2: + case 4: + return '[В следующий] dddd, [в] LT'; + case 3: + case 5: + case 6: + return '[В следующую] dddd, [в] LT'; + } + } + + if ($current->dayOfWeek === 2) { + return '[Во] dddd, [в] LT'; + } + + return '[В] dddd, [в] LT'; + }, + 'lastDay' => '[Вчера, в] LT', + 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($current->week !== $other->week) { + switch ($current->dayOfWeek) { + case 0: + return '[В прошлое] dddd, [в] LT'; + case 1: + case 2: + case 4: + return '[В прошлый] dddd, [в] LT'; + case 3: + case 5: + case 6: + return '[В прошлую] dddd, [в] LT'; + } + } + + if ($current->dayOfWeek === 2) { + return '[Во] dddd, [в] LT'; + } + + return '[В] dddd, [в] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + return $number.'-й'; + case 'D': + return $number.'-го'; + case 'w': + case 'W': + return $number.'-я'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночи'; + } + if ($hour < 12) { + return 'утра'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечера'; + }, + 'months' => ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'], + 'months_standalone' => ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'мая', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'months_short_standalone' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['воскресенье', 'понедельник', 'вторник', 'среду', 'четверг', 'пятницу', 'субботу'], + 'weekdays_standalone' => ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'], + 'weekdays_short' => ['вск', 'пнд', 'втр', 'срд', 'чтв', 'птн', 'сбт'], + 'weekdays_min' => ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'weekdays_regexp' => '/\[\s*(В|в)\s*((?:прошлую|следующую|эту)\s*)?\]\s*dddd/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ru_BY.php b/libraries/Carbon/src/Carbon/Lang/ru_BY.php new file mode 100644 index 00000000000..f23ca22596b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ru_BY.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ru_KG.php b/libraries/Carbon/src/Carbon/Lang/ru_KG.php new file mode 100644 index 00000000000..f23ca22596b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ru_KG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ru_KZ.php b/libraries/Carbon/src/Carbon/Lang/ru_KZ.php new file mode 100644 index 00000000000..f23ca22596b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ru_KZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ru_MD.php b/libraries/Carbon/src/Carbon/Lang/ru_MD.php new file mode 100644 index 00000000000..f23ca22596b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ru_MD.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ru_RU.php b/libraries/Carbon/src/Carbon/Lang/ru_RU.php new file mode 100644 index 00000000000..f23ca22596b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ru_RU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ru_UA.php b/libraries/Carbon/src/Carbon/Lang/ru_UA.php new file mode 100644 index 00000000000..bbc6adad012 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ru_UA.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RFC 2319 bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ru.php', [ + 'weekdays' => ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'], + 'weekdays_short' => ['вск', 'пнд', 'вто', 'срд', 'чтв', 'птн', 'суб'], + 'weekdays_min' => ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'су'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/rw.php b/libraries/Carbon/src/Carbon/Lang/rw.php new file mode 100644 index 00000000000..e55840ebbf9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/rw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/rw_RW.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/rw_RW.php b/libraries/Carbon/src/Carbon/Lang/rw_RW.php new file mode 100644 index 00000000000..0bbab384efa --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/rw_RW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rwanda Steve Murphy murf@e-tools.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Mutarama', 'Gashyantare', 'Werurwe', 'Mata', 'Gicuransi', 'Kamena', 'Nyakanga', 'Kanama', 'Nzeli', 'Ukwakira', 'Ugushyingo', 'Ukuboza'], + 'months_short' => ['Mut', 'Gas', 'Wer', 'Mat', 'Gic', 'Kam', 'Nya', 'Kan', 'Nze', 'Ukw', 'Ugu', 'Uku'], + 'weekdays' => ['Ku cyumweru', 'Kuwa mbere', 'Kuwa kabiri', 'Kuwa gatatu', 'Kuwa kane', 'Kuwa gatanu', 'Kuwa gatandatu'], + 'weekdays_short' => ['Mwe', 'Mbe', 'Kab', 'Gtu', 'Kan', 'Gnu', 'Gnd'], + 'weekdays_min' => ['Mwe', 'Mbe', 'Kab', 'Gtu', 'Kan', 'Gnu', 'Gnd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'second' => ':count vuna', // less reliable + 's' => ':count vuna', // less reliable + 'a_second' => ':count vuna', // less reliable + + 'year' => 'aka :count', + 'y' => 'aka :count', + 'a_year' => 'aka :count', + + 'month' => 'ezi :count', + 'm' => 'ezi :count', + 'a_month' => 'ezi :count', + + 'week' => ':count icyumweru', + 'w' => ':count icyumweru', + 'a_week' => ':count icyumweru', + + 'day' => ':count nsi', + 'd' => ':count nsi', + 'a_day' => ':count nsi', + + 'hour' => 'saha :count', + 'h' => 'saha :count', + 'a_hour' => 'saha :count', + + 'minute' => ':count -nzinya', + 'min' => ':count -nzinya', + 'a_minute' => ':count -nzinya', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/rwk.php b/libraries/Carbon/src/Carbon/Lang/rwk.php new file mode 100644 index 00000000000..f11ba53eaa9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/rwk.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sa.php b/libraries/Carbon/src/Carbon/Lang/sa.php new file mode 100644 index 00000000000..444356ef2fc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sa_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sa_IN.php b/libraries/Carbon/src/Carbon/Lang/sa_IN.php new file mode 100644 index 00000000000..cf63052768e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sa_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian project Christian Perrier bubulle@debian.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-MM-YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['रविवासर:', 'सोमवासर:', 'मंगलवासर:', 'बुधवासर:', 'बृहस्पतिवासरः', 'शुक्रवासर', 'शनिवासर:'], + 'weekdays_short' => ['रविः', 'सोम:', 'मंगल:', 'बुध:', 'बृहस्पतिः', 'शुक्र', 'शनि:'], + 'weekdays_min' => ['रविः', 'सोम:', 'मंगल:', 'बुध:', 'बृहस्पतिः', 'शुक्र', 'शनि:'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'minute' => ':count होरा', // less reliable + 'min' => ':count होरा', // less reliable + 'a_minute' => ':count होरा', // less reliable + + 'year' => ':count वर्ष', + 'y' => ':count वर्ष', + 'a_year' => ':count वर्ष', + + 'month' => ':count मास', + 'm' => ':count मास', + 'a_month' => ':count मास', + + 'week' => ':count सप्ताहः saptahaĥ', + 'w' => ':count सप्ताहः saptahaĥ', + 'a_week' => ':count सप्ताहः saptahaĥ', + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', + + 'hour' => ':count घण्टा', + 'h' => ':count घण्टा', + 'a_hour' => ':count घण्टा', + + 'second' => ':count द्वितीयः', + 's' => ':count द्वितीयः', + 'a_second' => ':count द्वितीयः', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sah.php b/libraries/Carbon/src/Carbon/Lang/sah.php new file mode 100644 index 00000000000..614c18c1075 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sah.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sah_RU.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sah_RU.php b/libraries/Carbon/src/Carbon/Lang/sah_RU.php new file mode 100644 index 00000000000..81ea65e8531 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sah_RU.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Valery Timiriliyev Valery Timiriliyev timiriliyev@gmail.com + */ +return array_replace_recursive(require __DIR__.'/ru.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['тохсунньу', 'олунньу', 'кулун тутар', 'муус устар', 'ыам ыйын', 'бэс ыйын', 'от ыйын', 'атырдьах ыйын', 'балаҕан ыйын', 'алтынньы', 'сэтинньи', 'ахсынньы'], + 'months_short' => ['тохс', 'олун', 'кул', 'муус', 'ыам', 'бэс', 'от', 'атыр', 'бал', 'алт', 'сэт', 'ахс'], + 'weekdays' => ['баскыһыанньа', 'бэнидиэнньик', 'оптуорунньук', 'сэрэдэ', 'чэппиэр', 'бээтинсэ', 'субуота'], + 'weekdays_short' => ['бс', 'бн', 'оп', 'ср', 'чп', 'бт', 'сб'], + 'weekdays_min' => ['бс', 'бн', 'оп', 'ср', 'чп', 'бт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/saq.php b/libraries/Carbon/src/Carbon/Lang/saq.php new file mode 100644 index 00000000000..aa09134c997 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/saq.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Tesiran', 'Teipa'], + 'weekdays' => ['Mderot ee are', 'Mderot ee kuni', 'Mderot ee ong’wan', 'Mderot ee inet', 'Mderot ee ile', 'Mderot ee sapa', 'Mderot ee kwe'], + 'weekdays_short' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], + 'weekdays_min' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], + 'months' => ['Lapa le obo', 'Lapa le waare', 'Lapa le okuni', 'Lapa le ong’wan', 'Lapa le imet', 'Lapa le ile', 'Lapa le sapa', 'Lapa le isiet', 'Lapa le saal', 'Lapa le tomon', 'Lapa le tomon obo', 'Lapa le tomon waare'], + 'months_short' => ['Obo', 'Waa', 'Oku', 'Ong', 'Ime', 'Ile', 'Sap', 'Isi', 'Saa', 'Tom', 'Tob', 'Tow'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sat.php b/libraries/Carbon/src/Carbon/Lang/sat.php new file mode 100644 index 00000000000..72d82b877bb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sat.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sat_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sat_IN.php b/libraries/Carbon/src/Carbon/Lang/sat_IN.php new file mode 100644 index 00000000000..e72843da407 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sat_IN.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रेल', 'मई', 'जुन', 'जुलाई', 'अगस्त', 'सितम्बर', 'अखथबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रेल', 'मई', 'जुन', 'जुलाई', 'अगस्त', 'सितम्बर', 'अखथबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['सिंगेमाँहाँ', 'ओतेमाँहाँ', 'बालेमाँहाँ', 'सागुनमाँहाँ', 'सारदीमाँहाँ', 'जारुममाँहाँ', 'ञुहुममाँहाँ'], + 'weekdays_short' => ['सिंगे', 'ओते', 'बाले', 'सागुन', 'सारदी', 'जारुम', 'ञुहुम'], + 'weekdays_min' => ['सिंगे', 'ओते', 'बाले', 'सागुन', 'सारदी', 'जारुम', 'ञुहुम'], + 'day_of_first_week_of_year' => 1, + + 'month' => ':count ńindạ cando', // less reliable + 'm' => ':count ńindạ cando', // less reliable + 'a_month' => ':count ńindạ cando', // less reliable + + 'week' => ':count mãhã', // less reliable + 'w' => ':count mãhã', // less reliable + 'a_week' => ':count mãhã', // less reliable + + 'hour' => ':count ᱥᱳᱱᱚ', // less reliable + 'h' => ':count ᱥᱳᱱᱚ', // less reliable + 'a_hour' => ':count ᱥᱳᱱᱚ', // less reliable + + 'minute' => ':count ᱯᱤᱞᱪᱩ', // less reliable + 'min' => ':count ᱯᱤᱞᱪᱩ', // less reliable + 'a_minute' => ':count ᱯᱤᱞᱪᱩ', // less reliable + + 'second' => ':count ar', // less reliable + 's' => ':count ar', // less reliable + 'a_second' => ':count ar', // less reliable + + 'year' => ':count ne̲s', + 'y' => ':count ne̲s', + 'a_year' => ':count ne̲s', + + 'day' => ':count ᱫᱤᱱ', + 'd' => ':count ᱫᱤᱱ', + 'a_day' => ':count ᱫᱤᱱ', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sbp.php b/libraries/Carbon/src/Carbon/Lang/sbp.php new file mode 100644 index 00000000000..1707fec37ad --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sbp.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Lwamilawu', 'Pashamihe'], + 'weekdays' => ['Mulungu', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alahamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Mul', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Mul', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Mupalangulwa', 'Mwitope', 'Mushende', 'Munyi', 'Mushende Magali', 'Mujimbi', 'Mushipepo', 'Mupuguto', 'Munyense', 'Mokhu', 'Musongandembwe', 'Muhaano'], + 'months_short' => ['Mup', 'Mwi', 'Msh', 'Mun', 'Mag', 'Muj', 'Msp', 'Mpg', 'Mye', 'Mok', 'Mus', 'Muh'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sc.php b/libraries/Carbon/src/Carbon/Lang/sc.php new file mode 100644 index 00000000000..d6630b12907 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sc.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sc_IT.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sc_IT.php b/libraries/Carbon/src/Carbon/Lang/sc_IT.php new file mode 100644 index 00000000000..b9c689c9da2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sc_IT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sardinian Translators Team Massimeddu Cireddu massimeddu@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD. MM. YY', + ], + 'months' => ['Ghennàrgiu', 'Freàrgiu', 'Martzu', 'Abrile', 'Maju', 'Làmpadas', 'Argiolas//Trìulas', 'Austu', 'Cabudanni', 'Santugaine//Ladàmine', 'Onniasantu//Santandria', 'Nadale//Idas'], + 'months_short' => ['Ghe', 'Fre', 'Mar', 'Abr', 'Maj', 'Làm', 'Arg', 'Aus', 'Cab', 'Lad', 'Onn', 'Nad'], + 'weekdays' => ['Domìnigu', 'Lunis', 'Martis', 'Mèrcuris', 'Giòbia', 'Chenàbura', 'Sàbadu'], + 'weekdays_short' => ['Dom', 'Lun', 'Mar', 'Mèr', 'Giò', 'Che', 'Sàb'], + 'weekdays_min' => ['Dom', 'Lun', 'Mar', 'Mèr', 'Giò', 'Che', 'Sàb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count mementu', // less reliable + 'min' => ':count mementu', // less reliable + 'a_minute' => ':count mementu', // less reliable + + 'year' => ':count annu', + 'y' => ':count annu', + 'a_year' => ':count annu', + + 'month' => ':count mese', + 'm' => ':count mese', + 'a_month' => ':count mese', + + 'week' => ':count chida', + 'w' => ':count chida', + 'a_week' => ':count chida', + + 'day' => ':count dí', + 'd' => ':count dí', + 'a_day' => ':count dí', + + 'hour' => ':count ora', + 'h' => ':count ora', + 'a_hour' => ':count ora', + + 'second' => ':count secundu', + 's' => ':count secundu', + 'a_second' => ':count secundu', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sd.php b/libraries/Carbon/src/Carbon/Lang/sd.php new file mode 100644 index 00000000000..70d15023c7e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sd.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'جنوري', + 'فيبروري', + 'مارچ', + 'اپريل', + 'مئي', + 'جون', + 'جولاءِ', + 'آگسٽ', + 'سيپٽمبر', + 'آڪٽوبر', + 'نومبر', + 'ڊسمبر', +]; + +$weekdays = [ + 'آچر', + 'سومر', + 'اڱارو', + 'اربع', + 'خميس', + 'جمع', + 'ڇنڇر', +]; + +/* + * Authors: + * - Narain Sagar + * - Sawood Alam + * - Narain Sagar + */ +return [ + 'year' => '{1}'.'هڪ سال'.'|:count '.'سال', + 'month' => '{1}'.'هڪ مهينو'.'|:count '.'مهينا', + 'week' => '{1}'.'ھڪ ھفتو'.'|:count '.'هفتا', + 'day' => '{1}'.'هڪ ڏينهن'.'|:count '.'ڏينهن', + 'hour' => '{1}'.'هڪ ڪلاڪ'.'|:count '.'ڪلاڪ', + 'minute' => '{1}'.'هڪ منٽ'.'|:count '.'منٽ', + 'second' => '{1}'.'چند سيڪنڊ'.'|:count '.'سيڪنڊ', + 'ago' => ':time اڳ', + 'from_now' => ':time پوء', + 'diff_yesterday' => 'ڪالهه', + 'diff_today' => 'اڄ', + 'diff_tomorrow' => 'سڀاڻي', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd، D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اڄ] LT', + 'nextDay' => '[سڀاڻي] LT', + 'nextWeek' => 'dddd [اڳين هفتي تي] LT', + 'lastDay' => '[ڪالهه] LT', + 'lastWeek' => '[گزريل هفتي] dddd [تي] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['صبح', 'شام'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => ['، ', ' ۽ '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sd_IN.php b/libraries/Carbon/src/Carbon/Lang/sd_IN.php new file mode 100644 index 00000000000..5efa5e07f1f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sd_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/sd.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['جنوري', 'فبروري', 'مارچ', 'اپريل', 'مي', 'جون', 'جولاءِ', 'آگسٽ', 'سيپٽيمبر', 'آڪٽوبر', 'نومبر', 'ڊسمبر'], + 'months_short' => ['جنوري', 'فبروري', 'مارچ', 'اپريل', 'مي', 'جون', 'جولاءِ', 'آگسٽ', 'سيپٽيمبر', 'آڪٽوبر', 'نومبر', 'ڊسمبر'], + 'weekdays' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'weekdays_short' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'weekdays_min' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sd_IN@devanagari.php b/libraries/Carbon/src/Carbon/Lang/sd_IN@devanagari.php new file mode 100644 index 00000000000..86c59983cb7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sd_IN@devanagari.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/sd.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फबरवरी', 'मार्चि', 'अप्रेल', 'मे', 'जूनि', 'जूलाइ', 'आगस्टु', 'सेप्टेंबरू', 'आक्टूबरू', 'नवंबरू', 'ॾिसंबरू'], + 'months_short' => ['जनवरी', 'फबरवरी', 'मार्चि', 'अप्रेल', 'मे', 'जूनि', 'जूलाइ', 'आगस्टु', 'सेप्टेंबरू', 'आक्टूबरू', 'नवंबरू', 'ॾिसंबरू'], + 'weekdays' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'weekdays_short' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'weekdays_min' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['म.पू.', 'म.नं.'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/se.php b/libraries/Carbon/src/Carbon/Lang/se.php new file mode 100644 index 00000000000..7266600d62b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/se.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Karamell + */ +return [ + 'year' => '{1}:count jahki|:count jagit', + 'a_year' => '{1}okta jahki|:count jagit', + 'y' => ':count j.', + 'month' => '{1}:count mánnu|:count mánut', + 'a_month' => '{1}okta mánnu|:count mánut', + 'm' => ':count mán.', + 'week' => '{1}:count vahkku|:count vahkku', + 'a_week' => '{1}okta vahkku|:count vahkku', + 'w' => ':count v.', + 'day' => '{1}:count beaivi|:count beaivvit', + 'a_day' => '{1}okta beaivi|:count beaivvit', + 'd' => ':count b.', + 'hour' => '{1}:count diimmu|:count diimmut', + 'a_hour' => '{1}okta diimmu|:count diimmut', + 'h' => ':count d.', + 'minute' => '{1}:count minuhta|:count minuhtat', + 'a_minute' => '{1}okta minuhta|:count minuhtat', + 'min' => ':count min.', + 'second' => '{1}:count sekunddat|:count sekunddat', + 'a_second' => '{1}moadde sekunddat|:count sekunddat', + 's' => ':count s.', + 'ago' => 'maŋit :time', + 'from_now' => ':time geažes', + 'diff_yesterday' => 'ikte', + 'diff_yesterday_regexp' => 'ikte(?:\\s+ti)?', + 'diff_today' => 'otne', + 'diff_today_regexp' => 'otne(?:\\s+ti)?', + 'diff_tomorrow' => 'ihttin', + 'diff_tomorrow_regexp' => 'ihttin(?:\\s+ti)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'MMMM D. [b.] YYYY', + 'LLL' => 'MMMM D. [b.] YYYY [ti.] HH:mm', + 'LLLL' => 'dddd, MMMM D. [b.] YYYY [ti.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[otne ti] LT', + 'nextDay' => '[ihttin ti] LT', + 'nextWeek' => 'dddd [ti] LT', + 'lastDay' => '[ikte ti] LT', + 'lastWeek' => '[ovddit] dddd [ti] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['ođđajagemánnu', 'guovvamánnu', 'njukčamánnu', 'cuoŋománnu', 'miessemánnu', 'geassemánnu', 'suoidnemánnu', 'borgemánnu', 'čakčamánnu', 'golggotmánnu', 'skábmamánnu', 'juovlamánnu'], + 'months_short' => ['ođđj', 'guov', 'njuk', 'cuo', 'mies', 'geas', 'suoi', 'borg', 'čakč', 'golg', 'skáb', 'juov'], + 'weekdays' => ['sotnabeaivi', 'vuossárga', 'maŋŋebárga', 'gaskavahkku', 'duorastat', 'bearjadat', 'lávvardat'], + 'weekdays_short' => ['sotn', 'vuos', 'maŋ', 'gask', 'duor', 'bear', 'láv'], + 'weekdays_min' => ['s', 'v', 'm', 'g', 'd', 'b', 'L'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ja '], + 'meridiem' => ['i.b.', 'e.b.'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/se_FI.php b/libraries/Carbon/src/Carbon/Lang/se_FI.php new file mode 100644 index 00000000000..fc5bb87cf8e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/se_FI.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/se.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['ođđajagemánnu', 'guovvamánnu', 'njukčamánnu', 'cuoŋománnu', 'miessemánnu', 'geassemánnu', 'suoidnemánnu', 'borgemánnu', 'čakčamánnu', 'golggotmánnu', 'skábmamánnu', 'juovlamánnu'], + 'months_short' => ['ođđj', 'guov', 'njuk', 'cuoŋ', 'mies', 'geas', 'suoi', 'borg', 'čakč', 'golg', 'skáb', 'juov'], + 'weekdays' => ['sotnabeaivi', 'mánnodat', 'disdat', 'gaskavahkku', 'duorastat', 'bearjadat', 'lávvordat'], + 'weekdays_short' => ['so', 'má', 'di', 'ga', 'du', 'be', 'lá'], + 'weekdays_min' => ['so', 'má', 'di', 'ga', 'du', 'be', 'lá'], + 'meridiem' => ['i', 'e'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/se_NO.php b/libraries/Carbon/src/Carbon/Lang/se_NO.php new file mode 100644 index 00000000000..8045721da0d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/se_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/se.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/se_SE.php b/libraries/Carbon/src/Carbon/Lang/se_SE.php new file mode 100644 index 00000000000..8045721da0d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/se_SE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/se.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/seh.php b/libraries/Carbon/src/Carbon/Lang/seh.php new file mode 100644 index 00000000000..0846272e4c0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/seh.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Dimingu', 'Chiposi', 'Chipiri', 'Chitatu', 'Chinai', 'Chishanu', 'Sabudu'], + 'weekdays_short' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], + 'weekdays_min' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], + 'months' => ['Janeiro', 'Fevreiro', 'Marco', 'Abril', 'Maio', 'Junho', 'Julho', 'Augusto', 'Setembro', 'Otubro', 'Novembro', 'Decembro'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Aug', 'Set', 'Otu', 'Nov', 'Dec'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'd [de] MMM [de] YYYY', + 'LLL' => 'd [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, d [de] MMMM [de] YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ses.php b/libraries/Carbon/src/Carbon/Lang/ses.php new file mode 100644 index 00000000000..1dbee5122e3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ses.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Adduha', 'Aluula'], + 'weekdays' => ['Alhadi', 'Atinni', 'Atalaata', 'Alarba', 'Alhamiisa', 'Alzuma', 'Asibti'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'month' => ':count alaada', // less reliable + 'm' => ':count alaada', // less reliable + 'a_month' => ':count alaada', // less reliable + + 'hour' => ':count ɲaajin', // less reliable + 'h' => ':count ɲaajin', // less reliable + 'a_hour' => ':count ɲaajin', // less reliable + + 'minute' => ':count zarbu', // less reliable + 'min' => ':count zarbu', // less reliable + 'a_minute' => ':count zarbu', // less reliable + + 'year' => ':count jiiri', + 'y' => ':count jiiri', + 'a_year' => ':count jiiri', + + 'week' => ':count jirbiiyye', + 'w' => ':count jirbiiyye', + 'a_week' => ':count jirbiiyye', + + 'day' => ':count zaari', + 'd' => ':count zaari', + 'a_day' => ':count zaari', + + 'second' => ':count ihinkante', + 's' => ':count ihinkante', + 'a_second' => ':count ihinkante', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sg.php b/libraries/Carbon/src/Carbon/Lang/sg.php new file mode 100644 index 00000000000..c56fd58d7d2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sg.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ND', 'LK'], + 'weekdays' => ['Bikua-ôko', 'Bïkua-ûse', 'Bïkua-ptâ', 'Bïkua-usïö', 'Bïkua-okü', 'Lâpôsö', 'Lâyenga'], + 'weekdays_short' => ['Bk1', 'Bk2', 'Bk3', 'Bk4', 'Bk5', 'Lâp', 'Lây'], + 'weekdays_min' => ['Bk1', 'Bk2', 'Bk3', 'Bk4', 'Bk5', 'Lâp', 'Lây'], + 'months' => ['Nyenye', 'Fulundïgi', 'Mbängü', 'Ngubùe', 'Bêläwü', 'Föndo', 'Lengua', 'Kükürü', 'Mvuka', 'Ngberere', 'Nabändüru', 'Kakauka'], + 'months_short' => ['Nye', 'Ful', 'Mbä', 'Ngu', 'Bêl', 'Fön', 'Len', 'Kük', 'Mvu', 'Ngb', 'Nab', 'Kak'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count dā', // less reliable + 'y' => ':count dā', // less reliable + 'a_year' => ':count dā', // less reliable + + 'week' => ':count bïkua-okü', // less reliable + 'w' => ':count bïkua-okü', // less reliable + 'a_week' => ':count bïkua-okü', // less reliable + + 'day' => ':count ziggawâ', // less reliable + 'd' => ':count ziggawâ', // less reliable + 'a_day' => ':count ziggawâ', // less reliable + + 'hour' => ':count yângâködörö', // less reliable + 'h' => ':count yângâködörö', // less reliable + 'a_hour' => ':count yângâködörö', // less reliable + + 'second' => ':count bïkua-ôko', // less reliable + 's' => ':count bïkua-ôko', // less reliable + 'a_second' => ':count bïkua-ôko', // less reliable + + 'month' => ':count Nze tî ngu', + 'm' => ':count Nze tî ngu', + 'a_month' => ':count Nze tî ngu', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sgs.php b/libraries/Carbon/src/Carbon/Lang/sgs.php new file mode 100644 index 00000000000..21de1dd2552 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sgs.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sgs_LT.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sgs_LT.php b/libraries/Carbon/src/Carbon/Lang/sgs_LT.php new file mode 100644 index 00000000000..3c30b450cf8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sgs_LT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Arnas Udovičius bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['sausė', 'vasarė', 'kuova', 'balondė', 'gegožės', 'bėrželė', 'lëpas', 'rogpjūtė', 'siejės', 'spalė', 'lapkrėstė', 'grůdė'], + 'months_short' => ['Sau', 'Vas', 'Kuo', 'Bal', 'Geg', 'Bėr', 'Lëp', 'Rgp', 'Sie', 'Spa', 'Lap', 'Grd'], + 'weekdays' => ['nedielės dëna', 'panedielis', 'oterninks', 'sereda', 'četvergs', 'petnīčė', 'sobata'], + 'weekdays_short' => ['Nd', 'Pn', 'Ot', 'Sr', 'Čt', 'Pt', 'Sb'], + 'weekdays_min' => ['Nd', 'Pn', 'Ot', 'Sr', 'Čt', 'Pt', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count mažos', // less reliable + 'min' => ':count mažos', // less reliable + 'a_minute' => ':count mažos', // less reliable + + 'year' => ':count metā', + 'y' => ':count metā', + 'a_year' => ':count metā', + + 'month' => ':count mienou', + 'm' => ':count mienou', + 'a_month' => ':count mienou', + + 'week' => ':count nedielė', + 'w' => ':count nedielė', + 'a_week' => ':count nedielė', + + 'day' => ':count dīna', + 'd' => ':count dīna', + 'a_day' => ':count dīna', + + 'hour' => ':count adīna', + 'h' => ':count adīna', + 'a_hour' => ':count adīna', + + 'second' => ':count Sekondė', + 's' => ':count Sekondė', + 'a_second' => ':count Sekondė', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sh.php b/libraries/Carbon/src/Carbon/Lang/sh.php new file mode 100644 index 00000000000..e3672a444af --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sh.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// @codeCoverageIgnoreStart +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(static function ($number) { + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + }, 'sh'); +} +// @codeCoverageIgnoreEnd + +/* + * Authors: + * - Томица Кораћ + * - Enrique Vidal + * - Christopher Dell + * - dmilisic + * - danijel + * - Miroslav Matkovic (mikki021) + */ +return [ + 'diff_now' => 'sada', + 'diff_yesterday' => 'juče', + 'diff_tomorrow' => 'sutra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count g.', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count m.', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count n.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count č.', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekund|:count sekunde|:count sekundi', + 's' => ':count s.', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time raniјe', + 'weekdays' => ['Nedelja', 'Ponedeljak', 'Utorak', 'Sreda', 'Četvrtak', 'Petak', 'Subota'], + 'weekdays_short' => ['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub'], + 'weekdays_min' => ['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub'], + 'months' => ['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Jun', 'Jul', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'], + 'list' => [', ', ' i '], + 'meridiem' => ['pre podne', 'po podne'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/shi.php b/libraries/Carbon/src/Carbon/Lang/shi.php new file mode 100644 index 00000000000..a6e8d0b1d38 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/shi.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ⵜⵉⴼⴰⵡⵜ', 'ⵜⴰⴷⴳⴳⵯⴰⵜ'], + 'weekdays' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵕⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⴰⵙⴰ', 'ⴰⵢⵏ', 'ⴰⵙⵉ', 'ⴰⴽⵕ', 'ⴰⴽⵡ', 'ⴰⵙⵉⵎ', 'ⴰⵙⵉⴹ'], + 'weekdays_min' => ['ⴰⵙⴰ', 'ⴰⵢⵏ', 'ⴰⵙⵉ', 'ⴰⴽⵕ', 'ⴰⴽⵡ', 'ⴰⵙⵉⵎ', 'ⴰⵙⵉⴹ'], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵜⵓⴱⵔ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⴰⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏ', 'ⴱⵕⴰ', 'ⵎⴰⵕ', 'ⵉⴱⵔ', 'ⵎⴰⵢ', 'ⵢⵓⵏ', 'ⵢⵓⵍ', 'ⵖⵓⵛ', 'ⵛⵓⵜ', 'ⴽⵜⵓ', 'ⵏⵓⵡ', 'ⴷⵓⵊ'], + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count aseggwas', + 'y' => ':count aseggwas', + 'a_year' => ':count aseggwas', + + 'month' => ':count ayyur', + 'm' => ':count ayyur', + 'a_month' => ':count ayyur', + + 'week' => ':count imalass', + 'w' => ':count imalass', + 'a_week' => ':count imalass', + + 'day' => ':count ass', + 'd' => ':count ass', + 'a_day' => ':count ass', + + 'hour' => ':count urɣ', // less reliable + 'h' => ':count urɣ', // less reliable + 'a_hour' => ':count urɣ', // less reliable + + 'minute' => ':count ⴰⵎⵥⵉ', // less reliable + 'min' => ':count ⴰⵎⵥⵉ', // less reliable + 'a_minute' => ':count ⴰⵎⵥⵉ', // less reliable + + 'second' => ':count sin', // less reliable + 's' => ':count sin', // less reliable + 'a_second' => ':count sin', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/shi_Latn.php b/libraries/Carbon/src/Carbon/Lang/shi_Latn.php new file mode 100644 index 00000000000..d6c40a3d2a4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/shi_Latn.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/shi.php', [ + 'meridiem' => ['tifawt', 'tadggʷat'], + 'weekdays' => ['asamas', 'aynas', 'asinas', 'akṛas', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_short' => ['asa', 'ayn', 'asi', 'akṛ', 'akw', 'asim', 'asiḍ'], + 'weekdays_min' => ['asa', 'ayn', 'asi', 'akṛ', 'akw', 'asim', 'asiḍ'], + 'months' => ['innayr', 'bṛayṛ', 'maṛṣ', 'ibrir', 'mayyu', 'yunyu', 'yulyuz', 'ɣuct', 'cutanbir', 'ktubr', 'nuwanbir', 'dujanbir'], + 'months_short' => ['inn', 'bṛa', 'maṛ', 'ibr', 'may', 'yun', 'yul', 'ɣuc', 'cut', 'ktu', 'nuw', 'duj'], + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'minute' => ':count agur', // less reliable + 'min' => ':count agur', // less reliable + 'a_minute' => ':count agur', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/shi_Tfng.php b/libraries/Carbon/src/Carbon/Lang/shi_Tfng.php new file mode 100644 index 00000000000..781730b5a00 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/shi_Tfng.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/shi.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/shn.php b/libraries/Carbon/src/Carbon/Lang/shn.php new file mode 100644 index 00000000000..7628cd7c25a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/shn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/shn_MM.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/shn_MM.php b/libraries/Carbon/src/Carbon/Lang/shn_MM.php new file mode 100644 index 00000000000..2da2d8f95a2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/shn_MM.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ubuntu Myanmar LoCo Team https://ubuntu-mm.net Bone Pyae Sone bone.burma@mail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY MMM OD dddd', + ], + 'months' => ['လိူၼ်ၵမ်', 'လိူၼ်သၢမ်', 'လိူၼ်သီ', 'လိူၼ်ႁႃႈ', 'လိူၼ်ႁူၵ်း', 'လိူၼ်ၸဵတ်း', 'လိူၼ်ပႅတ်ႇ', 'လိူၼ်ၵဝ်ႈ', 'လိူၼ်သိပ်း', 'လိူၼ်သိပ်းဢိတ်း', 'လိူၼ်သိပ်းဢိတ်းသွင်', 'လိူၼ်ၸဵင်'], + 'months_short' => ['လိူၼ်ၵမ်', 'လိူၼ်သၢမ်', 'လိူၼ်သီ', 'လိူၼ်ႁႃႈ', 'လိူၼ်ႁူၵ်း', 'လိူၼ်ၸဵတ်း', 'လိူၼ်ပႅတ်ႇ', 'လိူၼ်ၵဝ်ႈ', 'လိူၼ်သိပ်း', 'လိူၼ်သိပ်းဢိတ်း', 'လိူၼ်သိပ်းဢိတ်းသွင်', 'လိူၼ်ၸဵင်'], + 'weekdays' => ['ဝၼ်းဢႃးတိတ်ႉ', 'ဝၼ်းၸၼ်', 'ဝၼ်း​ဢၢင်း​ၵၢၼ်း', 'ဝၼ်းပူတ်ႉ', 'ဝၼ်းၽတ်း', 'ဝၼ်းသုၵ်း', 'ဝၼ်းသဝ်'], + 'weekdays_short' => ['တိတ့်', 'ၸၼ်', 'ၵၢၼ်း', 'ပုတ့်', 'ၽတ်း', 'သုၵ်း', 'သဝ်'], + 'weekdays_min' => ['တိတ့်', 'ၸၼ်', 'ၵၢၼ်း', 'ပုတ့်', 'ၽတ်း', 'သုၵ်း', 'သဝ်'], + 'alt_numbers' => ['႐႐', '႐႑', '႐႒', '႐႓', '႐႔', '႐႕', '႐႖', '႐႗', '႐႘', '႐႙', '႑႐', '႑႑', '႑႒', '႑႓', '႑႔', '႑႕', '႑႖', '႑႗', '႑႘', '႑႙', '႒႐', '႒႑', '႒႒', '႒႓', '႒႔', '႒႕', '႒႖', '႒႗', '႒႘', '႒႙', '႓႐', '႓႑', '႓႒', '႓႓', '႓႔', '႓႕', '႓႖', '႓႗', '႓႘', '႓႙', '႔႐', '႔႑', '႔႒', '႔႓', '႔႔', '႔႕', '႔႖', '႔႗', '႔႘', '႔႙', '႕႐', '႕႑', '႕႒', '႕႓', '႕႔', '႕႕', '႕႖', '႕႗', '႕႘', '႕႙', '႖႐', '႖႑', '႖႒', '႖႓', '႖႔', '႖႕', '႖႖', '႖႗', '႖႘', '႖႙', '႗႐', '႗႑', '႗႒', '႗႓', '႗႔', '႗႕', '႗႖', '႗႗', '႗႘', '႗႙', '႘႐', '႘႑', '႘႒', '႘႓', '႘႔', '႘႕', '႘႖', '႘႗', '႘႘', '႘႙', '႙႐', '႙႑', '႙႒', '႙႓', '႙႔', '႙႕', '႙႖', '႙႗', '႙႘', '႙႙'], + 'meridiem' => ['ၵၢင်ၼႂ်', 'တၢမ်းၶမ်ႈ'], + + 'month' => ':count လိူၼ်', // less reliable + 'm' => ':count လိူၼ်', // less reliable + 'a_month' => ':count လိူၼ်', // less reliable + + 'week' => ':count ဝၼ်း', // less reliable + 'w' => ':count ဝၼ်း', // less reliable + 'a_week' => ':count ဝၼ်း', // less reliable + + 'hour' => ':count ຕີ', // less reliable + 'h' => ':count ຕີ', // less reliable + 'a_hour' => ':count ຕີ', // less reliable + + 'minute' => ':count ເດັກ', // less reliable + 'min' => ':count ເດັກ', // less reliable + 'a_minute' => ':count ເດັກ', // less reliable + + 'second' => ':count ဢိုၼ်ႇ', // less reliable + 's' => ':count ဢိုၼ်ႇ', // less reliable + 'a_second' => ':count ဢိုၼ်ႇ', // less reliable + + 'year' => ':count ပီ', + 'y' => ':count ပီ', + 'a_year' => ':count ပီ', + + 'day' => ':count ກາງວັນ', + 'd' => ':count ກາງວັນ', + 'a_day' => ':count ກາງວັນ', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/shs.php b/libraries/Carbon/src/Carbon/Lang/shs.php new file mode 100644 index 00000000000..6500a5cce43 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/shs.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/shs_CA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/shs_CA.php b/libraries/Carbon/src/Carbon/Lang/shs_CA.php new file mode 100644 index 00000000000..4850338a944 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/shs_CA.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Neskie Manuel bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Pellkwet̓min', 'Pelctsipwen̓ten', 'Pellsqépts', 'Peslléwten', 'Pell7ell7é7llqten', 'Pelltspéntsk', 'Pelltqwelq̓wél̓t', 'Pellct̓éxel̓cten', 'Pesqelqlélten', 'Pesllwélsten', 'Pellc7ell7é7llcwten̓', 'Pelltetétq̓em'], + 'months_short' => ['Kwe', 'Tsi', 'Sqe', 'Éwt', 'Ell', 'Tsp', 'Tqw', 'Ct̓é', 'Qel', 'Wél', 'U7l', 'Tet'], + 'weekdays' => ['Sxetspesq̓t', 'Spetkesq̓t', 'Selesq̓t', 'Skellesq̓t', 'Smesesq̓t', 'Stselkstesq̓t', 'Stqmekstesq̓t'], + 'weekdays_short' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'weekdays_min' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count sqlélten', // less reliable + 'y' => ':count sqlélten', // less reliable + 'a_year' => ':count sqlélten', // less reliable + + 'month' => ':count swewll', // less reliable + 'm' => ':count swewll', // less reliable + 'a_month' => ':count swewll', // less reliable + + 'hour' => ':count seqwlút', // less reliable + 'h' => ':count seqwlút', // less reliable + 'a_hour' => ':count seqwlút', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/si.php b/libraries/Carbon/src/Carbon/Lang/si.php new file mode 100644 index 00000000000..505403d368a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/si.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - Malinda Weerasinghe (MalindaWMD) + */ +return [ + 'year' => '{1}වසර 1|වසර :count', + 'a_year' => '{1}වසරක්|වසර :count', + 'month' => '{1}මාස 1|මාස :count', + 'a_month' => '{1}මාසය|මාස :count', + 'week' => '{1}සති 1|සති :count', + 'a_week' => '{1}සතියක්|සති :count', + 'day' => '{1}දින 1|දින :count', + 'a_day' => '{1}දිනක්|දින :count', + 'hour' => '{1}පැය 1|පැය :count', + 'a_hour' => '{1}පැයක්|පැය :count', + 'minute' => '{1}මිනිත්තු 1|මිනිත්තු :count', + 'a_minute' => '{1}මිනිත්තුවක්|මිනිත්තු :count', + 'second' => '{1}තත්පර 1|තත්පර :count', + 'a_second' => '{1}තත්පර කිහිපයකට|තත්පර :count', + 'ago' => ':time කට පෙර', + 'from_now' => function ($time) { + if (preg_match('/දින \d/u', $time)) { + return $time.' න්'; + } + + return $time.' කින්'; + }, + 'before' => ':time කට පෙර', + 'after' => function ($time) { + if (preg_match('/දින \d/u', $time)) { + return $time.' න්'; + } + + return $time.' කින්'; + }, + 'diff_now' => 'දැන්', + 'diff_today' => 'අද', + 'diff_yesterday' => 'ඊයේ', + 'diff_tomorrow' => 'හෙට', + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY MMMM D', + 'LLL' => 'YYYY MMMM D, a h:mm', + 'LLLL' => 'YYYY MMMM D [වැනි] dddd, a h:mm:ss', + ], + 'calendar' => [ + 'sameDay' => '[අද] LT[ට]', + 'nextDay' => '[හෙට] LT[ට]', + 'nextWeek' => 'dddd LT[ට]', + 'lastDay' => '[ඊයේ] LT[ට]', + 'lastWeek' => '[පසුගිය] dddd LT[ට]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number වැනි', + 'meridiem' => ['පෙර වරු', 'පස් වරු', 'පෙ.ව.', 'ප.ව.'], + 'months' => ['ජනවාරි', 'පෙබරවාරි', 'මාර්තු', 'අප්‍රේල්', 'මැයි', 'ජූනි', 'ජූලි', 'අගෝස්තු', 'සැප්තැම්බර්', 'ඔක්තෝබර්', 'නොවැම්බර්', 'දෙසැම්බර්'], + 'months_short' => ['ජන', 'පෙබ', 'මාර්', 'අප්', 'මැයි', 'ජූනි', 'ජූලි', 'අගෝ', 'සැප්', 'ඔක්', 'නොවැ', 'දෙසැ'], + 'weekdays' => ['ඉරිදා', 'සඳුදා', 'අඟහරුවාදා', 'බදාදා', 'බ්‍රහස්පතින්දා', 'සිකුරාදා', 'සෙනසුරාදා'], + 'weekdays_short' => ['ඉරි', 'සඳු', 'අඟ', 'බදා', 'බ්‍රහ', 'සිකු', 'සෙන'], + 'weekdays_min' => ['ඉ', 'ස', 'අ', 'බ', 'බ්‍ර', 'සි', 'සෙ'], + 'first_day_of_week' => 1, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/si_LK.php b/libraries/Carbon/src/Carbon/Lang/si_LK.php new file mode 100644 index 00000000000..eb0395de2b1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/si_LK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/si.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sid.php b/libraries/Carbon/src/Carbon/Lang/sid.php new file mode 100644 index 00000000000..b9db4e99dc8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sid.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sid_ET.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sid_ET.php b/libraries/Carbon/src/Carbon/Lang/sid_ET.php new file mode 100644 index 00000000000..620190f8bb4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sid_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sambata', 'Sanyo', 'Maakisanyo', 'Roowe', 'Hamuse', 'Arbe', 'Qidaame'], + 'weekdays_short' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'weekdays_min' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['soodo', 'hawwaro'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sk.php b/libraries/Carbon/src/Carbon/Lang/sk.php new file mode 100644 index 00000000000..f5b51040287 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sk.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Martin Suja + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Ivan Stana + * - Akira Matsuda + * - Christopher Dell + * - James McKinney + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Jozef Fulop + * - Nicolás Hock Isaza + * - Tom Hughes + * - Simon Hürlimann (CyT) + * - jofi + * - Jakub ADAMEC + * - Marek Adamický + * - AlterwebStudio + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +$fromNow = function ($time) { + return 'o '.strtr($time, [ + 'hodina' => 'hodinu', + 'minúta' => 'minútu', + 'sekunda' => 'sekundu', + ]); +}; + +$ago = function ($time) { + $replacements = [ + '/\bhodina\b/' => 'hodinou', + '/\bminúta\b/' => 'minútou', + '/\bsekunda\b/' => 'sekundou', + '/\bdeň\b/u' => 'dňom', + '/\btýždeň\b/u' => 'týždňom', + '/\bmesiac\b/' => 'mesiacom', + '/\brok\b/' => 'rokom', + ]; + + $replacementsPlural = [ + '/\bhodiny\b/' => 'hodinami', + '/\bminúty\b/' => 'minútami', + '/\bsekundy\b/' => 'sekundami', + '/\bdni\b/' => 'dňami', + '/\btýždne\b/' => 'týždňami', + '/\bmesiace\b/' => 'mesiacmi', + '/\broky\b/' => 'rokmi', + ]; + + foreach ($replacements + $replacementsPlural as $pattern => $replacement) { + $time = preg_replace($pattern, $replacement, $time); + } + + return "pred $time"; +}; + +return [ + 'year' => ':count rok|:count roky|:count rokov', + 'a_year' => 'rok|:count roky|:count rokov', + 'y' => ':count r', + 'month' => ':count mesiac|:count mesiace|:count mesiacov', + 'a_month' => 'mesiac|:count mesiace|:count mesiacov', + 'm' => ':count m', + 'week' => ':count týždeň|:count týždne|:count týždňov', + 'a_week' => 'týždeň|:count týždne|:count týždňov', + 'w' => ':count t', + 'day' => ':count deň|:count dni|:count dní', + 'a_day' => 'deň|:count dni|:count dní', + 'd' => ':count d', + 'hour' => ':count hodina|:count hodiny|:count hodín', + 'a_hour' => 'hodina|:count hodiny|:count hodín', + 'h' => ':count h', + 'minute' => ':count minúta|:count minúty|:count minút', + 'a_minute' => 'minúta|:count minúty|:count minút', + 'min' => ':count min', + 'second' => ':count sekunda|:count sekundy|:count sekúnd', + 'a_second' => 'sekunda|:count sekundy|:count sekúnd', + 's' => ':count s', + 'millisecond' => ':count milisekunda|:count milisekundy|:count milisekúnd', + 'a_millisecond' => 'milisekunda|:count milisekundy|:count milisekúnd', + 'ms' => ':count ms', + 'microsecond' => ':count mikrosekunda|:count mikrosekundy|:count mikrosekúnd', + 'a_microsecond' => 'mikrosekunda|:count mikrosekundy|:count mikrosekúnd', + 'µs' => ':count µs', + + 'ago' => $ago, + 'from_now' => $fromNow, + 'before' => ':time pred', + 'after' => ':time po', + + 'hour_after' => ':count hodinu|:count hodiny|:count hodín', + 'minute_after' => ':count minútu|:count minúty|:count minút', + 'second_after' => ':count sekundu|:count sekundy|:count sekúnd', + + 'hour_before' => ':count hodinu|:count hodiny|:count hodín', + 'minute_before' => ':count minútu|:count minúty|:count minút', + 'second_before' => ':count sekundu|:count sekundy|:count sekúnd', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a '], + 'diff_now' => 'teraz', + 'diff_yesterday' => 'včera', + 'diff_tomorrow' => 'zajtra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'D. M. HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[dnes o] LT', + 'nextDay' => '[zajtra o] LT', + 'lastDay' => '[včera o] LT', + 'nextWeek' => 'dddd [o] LT', + 'lastWeek' => static function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 1: + case 2: + case 4: + case 5: + return '[minulý] dddd [o] LT'; //pondelok/utorok/štvrtok/piatok + default: + return '[minulá] dddd [o] LT'; + } + }, + 'sameElse' => 'L', + ], + 'weekdays' => ['nedeľa', 'pondelok', 'utorok', 'streda', 'štvrtok', 'piatok', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'uto', 'str', 'štv', 'pia', 'sob'], + 'weekdays_min' => ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'], + 'months' => ['január', 'február', 'marec', 'apríl', 'máj', 'jún', 'júl', 'august', 'september', 'október', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'meridiem' => ['dopoludnia', 'popoludní'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sk_SK.php b/libraries/Carbon/src/Carbon/Lang/sk_SK.php new file mode 100644 index 00000000000..f520cbdd486 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sk_SK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sk.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sl.php b/libraries/Carbon/src/Carbon/Lang/sl.php new file mode 100644 index 00000000000..7eff0b7bb10 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sl.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Miha Rebernik + * - Gal Jakič (morpheus7CS) + * - Glavić + * - Anže Časar + * - Lovro Tramšek (Lovro1107) + * - burut13 + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count leto|:count leti|:count leta|:count let', + 'y' => ':count leto|:count leti|:count leta|:count let', + 'month' => ':count mesec|:count meseca|:count mesece|:count mesecev', + 'm' => ':count mes.', + 'week' => ':count teden|:count tedna|:count tedne|:count tednov', + 'w' => ':count ted.', + 'day' => ':count dan|:count dni|:count dni|:count dni', + 'd' => ':count dan|:count dni|:count dni|:count dni', + 'hour' => ':count ura|:count uri|:count ure|:count ur', + 'h' => ':count h', + 'minute' => ':count minuta|:count minuti|:count minute|:count minut', + 'min' => ':count min.', + 'second' => ':count sekunda|:count sekundi|:count sekunde|:count sekund', + 'a_second' => '{1}nekaj sekund|:count sekunda|:count sekundi|:count sekunde|:count sekund', + 's' => ':count s', + + 'year_ago' => ':count letom|:count letoma|:count leti|:count leti', + 'y_ago' => ':count letom|:count letoma|:count leti|:count leti', + 'month_ago' => ':count mesecem|:count mesecema|:count meseci|:count meseci', + 'week_ago' => ':count tednom|:count tednoma|:count tedni|:count tedni', + 'day_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi', + 'd_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi', + 'hour_ago' => ':count uro|:count urama|:count urami|:count urami', + 'minute_ago' => ':count minuto|:count minutama|:count minutami|:count minutami', + 'second_ago' => ':count sekundo|:count sekundama|:count sekundami|:count sekundami', + + 'day_from_now' => ':count dan|:count dneva|:count dni|:count dni', + 'd_from_now' => ':count dan|:count dneva|:count dni|:count dni', + 'hour_from_now' => ':count uro|:count uri|:count ure|:count ur', + 'minute_from_now' => ':count minuto|:count minuti|:count minute|:count minut', + 'second_from_now' => ':count sekundo|:count sekundi|:count sekunde|:count sekund', + + 'ago' => 'pred :time', + 'from_now' => 'čez :time', + 'after' => ':time kasneje', + 'before' => ':time prej', + + 'diff_now' => 'ravnokar', + 'diff_today' => 'danes', + 'diff_today_regexp' => 'danes(?:\\s+ob)?', + 'diff_yesterday' => 'včeraj', + 'diff_yesterday_regexp' => 'včeraj(?:\\s+ob)?', + 'diff_tomorrow' => 'jutri', + 'diff_tomorrow_regexp' => 'jutri(?:\\s+ob)?', + 'diff_before_yesterday' => 'predvčerajšnjim', + 'diff_after_tomorrow' => 'pojutrišnjem', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'period_start_date' => 'od :date', + 'period_end_date' => 'do :date', + + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danes ob] LT', + 'nextDay' => '[jutri ob] LT', + 'nextWeek' => 'dddd [ob] LT', + 'lastDay' => '[včeraj ob] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[preteklo] [nedeljo] [ob] LT'; + case 1: + return '[pretekli] [ponedeljek] [ob] LT'; + case 2: + return '[pretekli] [torek] [ob] LT'; + case 3: + return '[preteklo] [sredo] [ob] LT'; + case 4: + return '[pretekli] [četrtek] [ob] LT'; + case 5: + return '[pretekli] [petek] [ob] LT'; + case 6: + return '[preteklo] [soboto] [ob] LT'; + } + }, + 'sameElse' => 'L', + ], + 'months' => ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'], + 'weekdays_min' => ['ne', 'po', 'to', 'sr', 'če', 'pe', 'so'], + 'list' => [', ', ' in '], + 'meridiem' => ['dopoldan', 'popoldan'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sl_SI.php b/libraries/Carbon/src/Carbon/Lang/sl_SI.php new file mode 100644 index 00000000000..aad7ab61d15 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sl_SI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sm.php b/libraries/Carbon/src/Carbon/Lang/sm.php new file mode 100644 index 00000000000..814c1ff6658 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sm.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sm_WS.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sm_WS.php b/libraries/Carbon/src/Carbon/Lang/sm_WS.php new file mode 100644 index 00000000000..d80a2d508a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sm_WS.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Ianuari', 'Fepuari', 'Mati', 'Aperila', 'Me', 'Iuni', 'Iulai', 'Auguso', 'Setema', 'Oketopa', 'Novema', 'Tesema'], + 'months_short' => ['Ian', 'Fep', 'Mat', 'Ape', 'Me', 'Iun', 'Iul', 'Aug', 'Set', 'Oke', 'Nov', 'Tes'], + 'weekdays' => ['Aso Sa', 'Aso Gafua', 'Aso Lua', 'Aso Lulu', 'Aso Tofi', 'Aso Farail', 'Aso To\'ana\'i'], + 'weekdays_short' => ['Aso Sa', 'Aso Gaf', 'Aso Lua', 'Aso Lul', 'Aso Tof', 'Aso Far', 'Aso To\''], + 'weekdays_min' => ['Aso Sa', 'Aso Gaf', 'Aso Lua', 'Aso Lul', 'Aso Tof', 'Aso Far', 'Aso To\''], + + 'hour' => ':count uati', // less reliable + 'h' => ':count uati', // less reliable + 'a_hour' => ':count uati', // less reliable + + 'minute' => ':count itiiti', // less reliable + 'min' => ':count itiiti', // less reliable + 'a_minute' => ':count itiiti', // less reliable + + 'second' => ':count lua', // less reliable + 's' => ':count lua', // less reliable + 'a_second' => ':count lua', // less reliable + + 'year' => ':count tausaga', + 'y' => ':count tausaga', + 'a_year' => ':count tausaga', + + 'month' => ':count māsina', + 'm' => ':count māsina', + 'a_month' => ':count māsina', + + 'week' => ':count vaiaso', + 'w' => ':count vaiaso', + 'a_week' => ':count vaiaso', + + 'day' => ':count aso', + 'd' => ':count aso', + 'a_day' => ':count aso', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/smn.php b/libraries/Carbon/src/Carbon/Lang/smn.php new file mode 100644 index 00000000000..71a4bbdfb8a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/smn.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ip.', 'ep.'], + 'weekdays' => ['pasepeeivi', 'vuossaargâ', 'majebaargâ', 'koskoho', 'tuorâstuv', 'vástuppeeivi', 'lávurduv'], + 'weekdays_short' => ['pas', 'vuo', 'maj', 'kos', 'tuo', 'vás', 'láv'], + 'weekdays_min' => ['pa', 'vu', 'ma', 'ko', 'tu', 'vá', 'lá'], + 'weekdays_standalone' => ['pasepeivi', 'vuossargâ', 'majebargâ', 'koskokko', 'tuorâstâh', 'vástuppeivi', 'lávurdâh'], + 'months' => ['uđđâivemáánu', 'kuovâmáánu', 'njuhčâmáánu', 'cuáŋuimáánu', 'vyesimáánu', 'kesimáánu', 'syeinimáánu', 'porgemáánu', 'čohčâmáánu', 'roovvâdmáánu', 'skammâmáánu', 'juovlâmáánu'], + 'months_short' => ['uđiv', 'kuovâ', 'njuhčâ', 'cuáŋui', 'vyesi', 'kesi', 'syeini', 'porge', 'čohčâ', 'roovvâd', 'skammâ', 'juovlâ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'H.mm', + 'LTS' => 'H.mm.ss', + 'L' => 'D.M.YYYY', + 'LL' => 'MMM D. YYYY', + 'LLL' => 'MMMM D. YYYY H.mm', + 'LLLL' => 'dddd, MMMM D. YYYY H.mm', + ], + + 'hour' => ':count äigi', // less reliable + 'h' => ':count äigi', // less reliable + 'a_hour' => ':count äigi', // less reliable + + 'year' => ':count ihe', + 'y' => ':count ihe', + 'a_year' => ':count ihe', + + 'month' => ':count mánuppaje', + 'm' => ':count mánuppaje', + 'a_month' => ':count mánuppaje', + + 'week' => ':count okko', + 'w' => ':count okko', + 'a_week' => ':count okko', + + 'day' => ':count peivi', + 'd' => ':count peivi', + 'a_day' => ':count peivi', + + 'minute' => ':count miinut', + 'min' => ':count miinut', + 'a_minute' => ':count miinut', + + 'second' => ':count nubbe', + 's' => ':count nubbe', + 'a_second' => ':count nubbe', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sn.php b/libraries/Carbon/src/Carbon/Lang/sn.php new file mode 100644 index 00000000000..d9eca1bc252 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sn.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['Svondo', 'Muvhuro', 'Chipiri', 'Chitatu', 'China', 'Chishanu', 'Mugovera'], + 'weekdays_short' => ['Svo', 'Muv', 'Chp', 'Cht', 'Chn', 'Chs', 'Mug'], + 'weekdays_min' => ['Sv', 'Mu', 'Cp', 'Ct', 'Cn', 'Cs', 'Mg'], + 'months' => ['Ndira', 'Kukadzi', 'Kurume', 'Kubvumbi', 'Chivabvu', 'Chikumi', 'Chikunguru', 'Nyamavhuvhu', 'Gunyana', 'Gumiguru', 'Mbudzi', 'Zvita'], + 'months_short' => ['Ndi', 'Kuk', 'Kur', 'Kub', 'Chv', 'Chk', 'Chg', 'Nya', 'Gun', 'Gum', 'Mbu', 'Zvi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => 'makore :count', + 'y' => 'makore :count', + 'a_year' => 'makore :count', + + 'month' => 'mwedzi :count', + 'm' => 'mwedzi :count', + 'a_month' => 'mwedzi :count', + + 'week' => 'vhiki :count', + 'w' => 'vhiki :count', + 'a_week' => 'vhiki :count', + + 'day' => 'mazuva :count', + 'd' => 'mazuva :count', + 'a_day' => 'mazuva :count', + + 'hour' => 'maawa :count', + 'h' => 'maawa :count', + 'a_hour' => 'maawa :count', + + 'minute' => 'minitsi :count', + 'min' => 'minitsi :count', + 'a_minute' => 'minitsi :count', + + 'second' => 'sekonzi :count', + 's' => 'sekonzi :count', + 'a_second' => 'sekonzi :count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/so.php b/libraries/Carbon/src/Carbon/Lang/so.php new file mode 100644 index 00000000000..1bdaaefbac6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/so.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Author: + * - Abdifatah Abdilahi(@abdifatahz) + */ +return [ + 'year' => ':count sanad|:count sanadood', + 'a_year' => 'sanad|:count sanadood', + 'y' => '{1}:countsn|{0}:countsns|]1,Inf[:countsn', + 'month' => ':count bil|:count bilood', + 'a_month' => 'bil|:count bilood', + 'm' => ':countbil', + 'week' => ':count isbuuc', + 'a_week' => 'isbuuc|:count isbuuc', + 'w' => ':countis', + 'day' => ':count maalin|:count maalmood', + 'a_day' => 'maalin|:count maalmood', + 'd' => ':countml', + 'hour' => ':count saac', + 'a_hour' => 'saacad|:count saac', + 'h' => ':countsc', + 'minute' => ':count daqiiqo', + 'a_minute' => 'daqiiqo|:count daqiiqo', + 'min' => ':countdq', + 'second' => ':count ilbidhiqsi', + 'a_second' => 'xooga ilbidhiqsiyo|:count ilbidhiqsi', + 's' => ':countil', + 'ago' => ':time kahor', + 'from_now' => ':time gudahood', + 'after' => ':time kedib', + 'before' => ':time kahor', + 'diff_now' => 'hada', + 'diff_today' => 'maanta', + 'diff_today_regexp' => 'maanta(?:\s+markay\s+(?:tahay|ahayd))?', + 'diff_yesterday' => 'shalayto', + 'diff_yesterday_regexp' => 'shalayto(?:\s+markay\s+ahayd)?', + 'diff_tomorrow' => 'beri', + 'diff_tomorrow_regexp' => 'beri(?:\s+markay\s+tahay)?', + 'diff_before_yesterday' => 'doraato', + 'diff_after_tomorrow' => 'saadanbe', + 'period_recurrences' => 'mar|:count jeer', + 'period_interval' => ':interval kasta', + 'period_start_date' => 'laga bilaabo :date', + 'period_end_date' => 'ilaa :date', + 'months' => ['Janaayo', 'Febraayo', 'Abriil', 'Maajo', 'Juun', 'Luuliyo', 'Agoosto', 'Sebteembar', 'Oktoobar', 'Nofeembar', 'Diseembar'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Abr', 'Mjo', 'Jun', 'Lyo', 'Agt', 'Seb', 'Okt', 'Nof', 'Dis'], + 'weekdays' => ['Axad', 'Isniin', 'Talaada', 'Arbaca', 'Khamiis', 'Jimce', 'Sabti'], + 'weekdays_short' => ['Axd', 'Isn', 'Tal', 'Arb', 'Kha', 'Jim', 'Sbt'], + 'weekdays_min' => ['Ax', 'Is', 'Ta', 'Ar', 'Kh', 'Ji', 'Sa'], + 'list' => [', ', ' and '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'calendar' => [ + 'sameDay' => '[Maanta markay tahay] LT', + 'nextDay' => '[Beri markay tahay] LT', + 'nextWeek' => 'dddd [markay tahay] LT', + 'lastDay' => '[Shalay markay ahayd] LT', + 'lastWeek' => '[Hore] dddd [Markay ahayd] LT', + 'sameElse' => 'L', + ], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/so_DJ.php b/libraries/Carbon/src/Carbon/Lang/so_DJ.php new file mode 100644 index 00000000000..fdcd137e94c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/so_DJ.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/so.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/so_ET.php b/libraries/Carbon/src/Carbon/Lang/so_ET.php new file mode 100644 index 00000000000..605132d583f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/so_ET.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/so_KE.php b/libraries/Carbon/src/Carbon/Lang/so_KE.php new file mode 100644 index 00000000000..605132d583f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/so_KE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/so_SO.php b/libraries/Carbon/src/Carbon/Lang/so_SO.php new file mode 100644 index 00000000000..605132d583f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/so_SO.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sq.php b/libraries/Carbon/src/Carbon/Lang/sq.php new file mode 100644 index 00000000000..7368ee4edda --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sq.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Fadion Dashi + */ +return [ + 'year' => ':count vit|:count vjet', + 'a_year' => 'një vit|:count vite', + 'y' => ':count v.', + 'month' => ':count muaj', + 'a_month' => 'një muaj|:count muaj', + 'm' => ':count muaj', + 'week' => ':count javë', + 'a_week' => ':count javë|:count javë', + 'w' => ':count j.', + 'day' => ':count ditë', + 'a_day' => 'një ditë|:count ditë', + 'd' => ':count d.', + 'hour' => ':count orë', + 'a_hour' => 'një orë|:count orë', + 'h' => ':count o.', + 'minute' => ':count minutë|:count minuta', + 'a_minute' => 'një minutë|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekondë|:count sekonda', + 'a_second' => 'disa sekonda|:count sekonda', + 's' => ':count s.', + 'ago' => ':time më parë', + 'from_now' => 'në :time', + 'after' => ':time pas', + 'before' => ':time para', + 'diff_now' => 'tani', + 'diff_today' => 'Sot', + 'diff_today_regexp' => 'Sot(?:\\s+në)?', + 'diff_yesterday' => 'dje', + 'diff_yesterday_regexp' => 'Dje(?:\\s+në)?', + 'diff_tomorrow' => 'nesër', + 'diff_tomorrow_regexp' => 'Nesër(?:\\s+në)?', + 'diff_before_yesterday' => 'pardje', + 'diff_after_tomorrow' => 'pasnesër', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Sot në] LT', + 'nextDay' => '[Nesër në] LT', + 'nextWeek' => 'dddd [në] LT', + 'lastDay' => '[Dje në] LT', + 'lastWeek' => 'dddd [e kaluar në] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'meridiem' => ['PD', 'MD'], + 'months' => ['janar', 'shkurt', 'mars', 'prill', 'maj', 'qershor', 'korrik', 'gusht', 'shtator', 'tetor', 'nëntor', 'dhjetor'], + 'months_short' => ['jan', 'shk', 'mar', 'pri', 'maj', 'qer', 'kor', 'gus', 'sht', 'tet', 'nën', 'dhj'], + 'weekdays' => ['e diel', 'e hënë', 'e martë', 'e mërkurë', 'e enjte', 'e premte', 'e shtunë'], + 'weekdays_short' => ['die', 'hën', 'mar', 'mër', 'enj', 'pre', 'sht'], + 'weekdays_min' => ['d', 'h', 'ma', 'më', 'e', 'p', 'sh'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' dhe '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sq_AL.php b/libraries/Carbon/src/Carbon/Lang/sq_AL.php new file mode 100644 index 00000000000..faa920b09a7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sq_AL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sq.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sq_MK.php b/libraries/Carbon/src/Carbon/Lang/sq_MK.php new file mode 100644 index 00000000000..968536cdaa5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sq_MK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sq.php', [ + 'formats' => [ + 'L' => 'D.M.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sq_XK.php b/libraries/Carbon/src/Carbon/Lang/sq_XK.php new file mode 100644 index 00000000000..968536cdaa5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sq_XK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sq.php', [ + 'formats' => [ + 'L' => 'D.M.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sr.php b/libraries/Carbon/src/Carbon/Lang/sr.php new file mode 100644 index 00000000000..3a3aae2ee41 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + * - Milos Sakovic + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count g.', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count mj.', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count ned.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count č.', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 's' => ':count sek.', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => 'pre :time', + + 'year_from_now' => ':count godinu|:count godine|:count godina', + 'year_ago' => ':count godinu|:count godine|:count godina', + 'week_from_now' => ':count nedelju|:count nedelje|:count nedelja', + 'week_ago' => ':count nedelju|:count nedelje|:count nedelja', + + 'diff_now' => 'upravo sada', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'juče', + 'diff_yesterday_regexp' => 'juče(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'diff_before_yesterday' => 'prekjuče', + 'diff_after_tomorrow' => 'preksutra', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u nedelju u] LT'; + case 3: + return '[u sredu u] LT'; + case 6: + return '[u subotu u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[juče u] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[prošle nedelje u] LT'; + case 1: + return '[prošlog ponedeljka u] LT'; + case 2: + return '[prošlog utorka u] LT'; + case 3: + return '[prošle srede u] LT'; + case 4: + return '[prošlog četvrtka u] LT'; + case 5: + return '[prošlog petka u] LT'; + default: + return '[prošle subote u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mart', 'april', 'maj', 'jun', 'jul', 'avgust', 'septembar', 'oktobar', 'novembar', 'decembar'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj', 'jun', 'jul', 'avg.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sre.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sr_Cyrl.php b/libraries/Carbon/src/Carbon/Lang/sr_Cyrl.php new file mode 100644 index 00000000000..a3f2a346648 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_Cyrl.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + * - Nikola Zeravcic + * - Milos Sakovic + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +return [ + 'year' => ':count година|:count године|:count година', + 'y' => ':count г.', + 'month' => ':count месец|:count месеца|:count месеци', + 'm' => ':count м.', + 'week' => ':count недеља|:count недеље|:count недеља', + 'w' => ':count нед.', + 'day' => ':count дан|:count дана|:count дана', + 'd' => ':count д.', + 'hour' => ':count сат|:count сата|:count сати', + 'h' => ':count ч.', + 'minute' => ':count минут|:count минута|:count минута', + 'min' => ':count мин.', + 'second' => ':count секунд|:count секунде|:count секунди', + 's' => ':count сек.', + 'ago' => 'пре :time', + 'from_now' => 'за :time', + 'after' => ':time након', + 'before' => ':time пре', + 'year_from_now' => ':count годину|:count године|:count година', + 'year_ago' => ':count годину|:count године|:count година', + 'week_from_now' => ':count недељу|:count недеље|:count недеља', + 'week_ago' => ':count недељу|:count недеље|:count недеља', + 'diff_now' => 'управо сада', + 'diff_today' => 'данас', + 'diff_today_regexp' => 'данас(?:\\s+у)?', + 'diff_yesterday' => 'јуче', + 'diff_yesterday_regexp' => 'јуче(?:\\s+у)?', + 'diff_tomorrow' => 'сутра', + 'diff_tomorrow_regexp' => 'сутра(?:\\s+у)?', + 'diff_before_yesterday' => 'прекјуче', + 'diff_after_tomorrow' => 'прекосутра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[данас у] LT', + 'nextDay' => '[сутра у] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[у недељу у] LT'; + case 3: + return '[у среду у] LT'; + case 6: + return '[у суботу у] LT'; + default: + return '[у] dddd [у] LT'; + } + }, + 'lastDay' => '[јуче у] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[прошле недеље у] LT'; + case 1: + return '[прошлог понедељка у] LT'; + case 2: + return '[прошлог уторка у] LT'; + case 3: + return '[прошле среде у] LT'; + case 4: + return '[прошлог четвртка у] LT'; + case 5: + return '[прошлог петка у] LT'; + default: + return '[прошле суботе у] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'], + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'], + 'weekdays_min' => ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_BA.php b/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_BA.php new file mode 100644 index 00000000000..8f67b8b501b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_BA.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_BA'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Cyrl.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.yy.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], + 'weekdays' => ['недјеља', 'понедељак', 'уторак', 'сриједа', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'ут.', 'ср.', 'чет.', 'пет.', 'суб.'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_ME.php b/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_ME.php new file mode 100644 index 00000000000..f84a4f7b7a3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_ME.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Glavić + * - Milos Sakovic + */ + +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_ME'); +} +// @codeCoverageIgnoreEnd + +return [ + 'year' => ':count година|:count године|:count година', + 'y' => ':count г.', + 'month' => ':count мјесец|:count мјесеца|:count мјесеци', + 'm' => ':count мј.', + 'week' => ':count недјеља|:count недјеље|:count недјеља', + 'w' => ':count нед.', + 'day' => ':count дан|:count дана|:count дана', + 'd' => ':count д.', + 'hour' => ':count сат|:count сата|:count сати', + 'h' => ':count ч.', + 'minute' => ':count минут|:count минута|:count минута', + 'min' => ':count мин.', + 'second' => ':count секунд|:count секунде|:count секунди', + 's' => ':count сек.', + 'ago' => 'прије :time', + 'from_now' => 'за :time', + 'after' => ':time након', + 'before' => ':time прије', + + 'year_from_now' => ':count годину|:count године|:count година', + 'year_ago' => ':count годину|:count године|:count година', + + 'week_from_now' => ':count недјељу|:count недјеље|:count недјеља', + 'week_ago' => ':count недјељу|:count недјеље|:count недјеља', + + 'diff_now' => 'управо сада', + 'diff_today' => 'данас', + 'diff_today_regexp' => 'данас(?:\\s+у)?', + 'diff_yesterday' => 'јуче', + 'diff_yesterday_regexp' => 'јуче(?:\\s+у)?', + 'diff_tomorrow' => 'сутра', + 'diff_tomorrow_regexp' => 'сутра(?:\\s+у)?', + 'diff_before_yesterday' => 'прекјуче', + 'diff_after_tomorrow' => 'прекосјутра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[данас у] LT', + 'nextDay' => '[сутра у] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[у недељу у] LT'; + case 3: + return '[у среду у] LT'; + case 6: + return '[у суботу у] LT'; + default: + return '[у] dddd [у] LT'; + } + }, + 'lastDay' => '[јуче у] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[прошле недеље у] LT'; + case 1: + return '[прошлог понедељка у] LT'; + case 2: + return '[прошлог уторка у] LT'; + case 3: + return '[прошле среде у] LT'; + case 4: + return '[прошлог четвртка у] LT'; + case 5: + return '[прошлог петка у] LT'; + default: + return '[прошле суботе у] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'], + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'], + 'weekdays_min' => ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_XK.php b/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_XK.php new file mode 100644 index 00000000000..2080206882c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_Cyrl_XK.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_XK'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Cyrl_BA.php', [ + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sr_Latn.php b/libraries/Carbon/src/Carbon/Lang/sr_Latn.php new file mode 100644 index 00000000000..0f3cf1a2066 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_Latn.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sr_Latn_BA.php b/libraries/Carbon/src/Carbon/Lang/sr_Latn_BA.php new file mode 100644 index 00000000000..650a142acae --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_Latn_BA.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_BA'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Latn.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.yy.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], + 'weekdays' => ['nedjelja', 'ponedeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'ut.', 'sr.', 'čet.', 'pet.', 'sub.'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sr_Latn_ME.php b/libraries/Carbon/src/Carbon/Lang/sr_Latn_ME.php new file mode 100644 index 00000000000..4b6205afd65 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_Latn_ME.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Glavić + * - Milos Sakovic + */ + +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_ME'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr.php', [ + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count nedjelja|:count nedjelje|:count nedjelja', + 'second' => ':count sekund|:count sekunde|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time nakon', + 'before' => ':time prije', + 'week_from_now' => ':count nedjelju|:count nedjelje|:count nedjelja', + 'week_ago' => ':count nedjelju|:count nedjelje|:count nedjelja', + 'second_ago' => ':count sekund|:count sekunde|:count sekundi', + 'diff_tomorrow' => 'sjutra', + 'calendar' => [ + 'nextDay' => '[sjutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u nedjelju u] LT'; + case 3: + return '[u srijedu u] LT'; + case 6: + return '[u subotu u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[prošle nedjelje u] LT'; + case 1: + return '[prošle nedjelje u] LT'; + case 2: + return '[prošlog utorka u] LT'; + case 3: + return '[prošle srijede u] LT'; + case 4: + return '[prošlog četvrtka u] LT'; + case 5: + return '[prošlog petka u] LT'; + default: + return '[prošle subote u] LT'; + } + }, + ], + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sr_Latn_XK.php b/libraries/Carbon/src/Carbon/Lang/sr_Latn_XK.php new file mode 100644 index 00000000000..683e1b1f4e5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_Latn_XK.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_XK'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Latn_BA.php', [ + 'weekdays' => ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'četvrtak', 'petak', 'subota'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sr_ME.php b/libraries/Carbon/src/Carbon/Lang/sr_ME.php new file mode 100644 index 00000000000..cb9b2d4fb6b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_ME.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr_Latn_ME.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sr_RS.php b/libraries/Carbon/src/Carbon/Lang/sr_RS.php new file mode 100644 index 00000000000..4326d397a72 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_RS.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sr_YU, sr_CS locale Danilo Segan bug-glibc-locales@gnu.org + */ +return require __DIR__.'/sr_Cyrl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sr_RS@latin.php b/libraries/Carbon/src/Carbon/Lang/sr_RS@latin.php new file mode 100644 index 00000000000..0f3cf1a2066 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sr_RS@latin.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ss.php b/libraries/Carbon/src/Carbon/Lang/ss.php new file mode 100644 index 00000000000..1d316ad1c0c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ss.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Nicolai Davies + */ +return [ + 'year' => '{1}umnyaka|:count iminyaka', + 'month' => '{1}inyanga|:count tinyanga', + 'week' => '{1}:count liviki|:count emaviki', + 'day' => '{1}lilanga|:count emalanga', + 'hour' => '{1}lihora|:count emahora', + 'minute' => '{1}umzuzu|:count emizuzu', + 'second' => '{1}emizuzwana lomcane|:count mzuzwana', + 'ago' => 'wenteka nga :time', + 'from_now' => 'nga :time', + 'diff_yesterday' => 'Itolo', + 'diff_yesterday_regexp' => 'Itolo(?:\\s+nga)?', + 'diff_today' => 'Namuhla', + 'diff_today_regexp' => 'Namuhla(?:\\s+nga)?', + 'diff_tomorrow' => 'Kusasa', + 'diff_tomorrow_regexp' => 'Kusasa(?:\\s+nga)?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Namuhla nga] LT', + 'nextDay' => '[Kusasa nga] LT', + 'nextWeek' => 'dddd [nga] LT', + 'lastDay' => '[Itolo nga] LT', + 'lastWeek' => 'dddd [leliphelile] [nga] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'e' : ( + ($lastDigit === 1 || $lastDigit === 2) ? 'a' : 'e' + ) + ); + }, + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'ekuseni'; + } + if ($hour < 15) { + return 'emini'; + } + if ($hour < 19) { + return 'entsambama'; + } + + return 'ebusuku'; + }, + 'months' => ['Bhimbidvwane', 'Indlovana', 'Indlov\'lenkhulu', 'Mabasa', 'Inkhwekhweti', 'Inhlaba', 'Kholwane', 'Ingci', 'Inyoni', 'Imphala', 'Lweti', 'Ingongoni'], + 'months_short' => ['Bhi', 'Ina', 'Inu', 'Mab', 'Ink', 'Inh', 'Kho', 'Igc', 'Iny', 'Imp', 'Lwe', 'Igo'], + 'weekdays' => ['Lisontfo', 'Umsombuluko', 'Lesibili', 'Lesitsatfu', 'Lesine', 'Lesihlanu', 'Umgcibelo'], + 'weekdays_short' => ['Lis', 'Umb', 'Lsb', 'Les', 'Lsi', 'Lsh', 'Umg'], + 'weekdays_min' => ['Li', 'Us', 'Lb', 'Lt', 'Ls', 'Lh', 'Ug'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ss_ZA.php b/libraries/Carbon/src/Carbon/Lang/ss_ZA.php new file mode 100644 index 00000000000..686f409f9cf --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ss_ZA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ss.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/st.php b/libraries/Carbon/src/Carbon/Lang/st.php new file mode 100644 index 00000000000..5067bf31735 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/st.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/st_ZA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/st_ZA.php b/libraries/Carbon/src/Carbon/Lang/st_ZA.php new file mode 100644 index 00000000000..7821998b1af --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/st_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Pherekgong', 'Hlakola', 'Tlhakubele', 'Mmese', 'Motsheanong', 'Phupjane', 'Phupu', 'Phato', 'Leotse', 'Mphalane', 'Pudungwana', 'Tshitwe'], + 'months_short' => ['Phe', 'Hla', 'TlH', 'Mme', 'Mot', 'Jan', 'Upu', 'Pha', 'Leo', 'Mph', 'Pud', 'Tsh'], + 'weekdays' => ['Sontaha', 'Mantaha', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Moqebelo'], + 'weekdays_short' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'weekdays_min' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'day_of_first_week_of_year' => 1, + + 'week' => ':count Sontaha', // less reliable + 'w' => ':count Sontaha', // less reliable + 'a_week' => ':count Sontaha', // less reliable + + 'day' => ':count letsatsi', // less reliable + 'd' => ':count letsatsi', // less reliable + 'a_day' => ':count letsatsi', // less reliable + + 'hour' => ':count sešupanako', // less reliable + 'h' => ':count sešupanako', // less reliable + 'a_hour' => ':count sešupanako', // less reliable + + 'minute' => ':count menyane', // less reliable + 'min' => ':count menyane', // less reliable + 'a_minute' => ':count menyane', // less reliable + + 'second' => ':count thusa', // less reliable + 's' => ':count thusa', // less reliable + 'a_second' => ':count thusa', // less reliable + + 'year' => ':count selemo', + 'y' => ':count selemo', + 'a_year' => ':count selemo', + + 'month' => ':count kgwedi', + 'm' => ':count kgwedi', + 'a_month' => ':count kgwedi', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sv.php b/libraries/Carbon/src/Carbon/Lang/sv.php new file mode 100644 index 00000000000..0ef254358af --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sv.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Kristoffer Snabb + * - JD Isaacks + * - Jens Herlevsen + * - Nightpine + * - Anders Nygren (litemerafrukt) + */ +return [ + 'year' => ':count år', + 'a_year' => 'ett år|:count år', + 'y' => ':count år', + 'month' => ':count månad|:count månader', + 'a_month' => 'en månad|:count månader', + 'm' => ':count mån', + 'week' => ':count vecka|:count veckor', + 'a_week' => 'en vecka|:count veckor', + 'w' => ':count v', + 'day' => ':count dag|:count dagar', + 'a_day' => 'en dag|:count dagar', + 'd' => ':count dgr', + 'hour' => ':count timme|:count timmar', + 'a_hour' => 'en timme|:count timmar', + 'h' => ':count tim', + 'minute' => ':count minut|:count minuter', + 'a_minute' => 'en minut|:count minuter', + 'min' => ':count min', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'några sekunder|:count sekunder', + 's' => ':count s', + 'ago' => 'för :time sedan', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time före', + 'diff_now' => 'nu', + 'diff_today' => 'I dag', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'I går', + 'diff_tomorrow' => 'i morgon', + 'diff_tomorrow_regexp' => 'I morgon', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [kl.] HH:mm', + 'LLLL' => 'dddd D MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[I dag] LT', + 'nextDay' => '[I morgon] LT', + 'nextWeek' => '[På] dddd LT', + 'lastDay' => '[I går] LT', + 'lastWeek' => '[I] dddd[s] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'e' : ( + ($lastDigit === 1 || $lastDigit === 2) ? 'a' : 'e' + ) + ); + }, + 'months' => ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'], + 'weekdays_short' => ['sön', 'mån', 'tis', 'ons', 'tors', 'fre', 'lör'], + 'weekdays_min' => ['sö', 'må', 'ti', 'on', 'to', 'fr', 'lö'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' och '], + 'meridiem' => ['fm', 'em'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sv_AX.php b/libraries/Carbon/src/Carbon/Lang/sv_AX.php new file mode 100644 index 00000000000..6a7092d5ec6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sv_AX.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sv.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-dd', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sv_FI.php b/libraries/Carbon/src/Carbon/Lang/sv_FI.php new file mode 100644 index 00000000000..5a0484f96af --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sv_FI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sv.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sv_SE.php b/libraries/Carbon/src/Carbon/Lang/sv_SE.php new file mode 100644 index 00000000000..5a0484f96af --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sv_SE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sv.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/sw.php b/libraries/Carbon/src/Carbon/Lang/sw.php new file mode 100644 index 00000000000..2068c4a03b5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sw.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - leyluj + * - Josh Soref + * - ryanhart2 + */ +return [ + 'year' => 'mwaka :count|miaka :count', + 'a_year' => 'mwaka mmoja|miaka :count', + 'y' => 'mwaka :count|miaka :count', + 'month' => 'mwezi :count|miezi :count', + 'a_month' => 'mwezi mmoja|miezi :count', + 'm' => 'mwezi :count|miezi :count', + 'week' => 'wiki :count', + 'a_week' => 'wiki mmoja|wiki :count', + 'w' => 'w. :count', + 'day' => 'siku :count', + 'a_day' => 'siku moja|masiku :count', + 'd' => 'si. :count', + 'hour' => 'saa :count|masaa :count', + 'a_hour' => 'saa limoja|masaa :count', + 'h' => 'saa :count|masaa :count', + 'minute' => 'dakika :count', + 'a_minute' => 'dakika moja|dakika :count', + 'min' => 'd. :count', + 'second' => 'sekunde :count', + 'a_second' => 'hivi punde|sekunde :count', + 's' => 'se. :count', + 'ago' => 'tokea :time', + 'from_now' => ':time baadaye', + 'after' => ':time baada', + 'before' => ':time kabla', + 'diff_now' => 'sasa hivi', + 'diff_today' => 'leo', + 'diff_today_regexp' => 'leo(?:\\s+saa)?', + 'diff_yesterday' => 'jana', + 'diff_tomorrow' => 'kesho', + 'diff_tomorrow_regexp' => 'kesho(?:\\s+saa)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[leo saa] LT', + 'nextDay' => '[kesho saa] LT', + 'nextWeek' => '[wiki ijayo] dddd [saat] LT', + 'lastDay' => '[jana] LT', + 'lastWeek' => '[wiki iliyopita] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpl', 'Jtat', 'Jnne', 'Jtan', 'Alh', 'Ijm', 'Jmos'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' na '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/sw_CD.php b/libraries/Carbon/src/Carbon/Lang/sw_CD.php new file mode 100644 index 00000000000..0b1ef5a236d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sw_CD.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sw_KE.php b/libraries/Carbon/src/Carbon/Lang/sw_KE.php new file mode 100644 index 00000000000..22e64cc2d63 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sw_KE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kamusi Project Martin Benjamin locales@kamusi.org + */ +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['asubuhi', 'alasiri'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sw_TZ.php b/libraries/Carbon/src/Carbon/Lang/sw_TZ.php new file mode 100644 index 00000000000..f20156ddf0d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sw_TZ.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kamusi Project Martin Benjamin locales@kamusi.org + */ +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['asubuhi', 'alasiri'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/sw_UG.php b/libraries/Carbon/src/Carbon/Lang/sw_UG.php new file mode 100644 index 00000000000..0b1ef5a236d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/sw_UG.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/szl.php b/libraries/Carbon/src/Carbon/Lang/szl.php new file mode 100644 index 00000000000..27df3ce0f8a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/szl.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/szl_PL.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/szl_PL.php b/libraries/Carbon/src/Carbon/Lang/szl_PL.php new file mode 100644 index 00000000000..b2f2e1b5ae0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/szl_PL.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - szl_PL locale Przemyslaw Buczkowski libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['styczyń', 'luty', 'merc', 'kwjeciyń', 'moj', 'czyrwjyń', 'lipjyń', 'siyrpjyń', 'wrzesiyń', 'październik', 'listopad', 'grudziyń'], + 'months_short' => ['sty', 'lut', 'mer', 'kwj', 'moj', 'czy', 'lip', 'siy', 'wrz', 'paź', 'lis', 'gru'], + 'weekdays' => ['niydziela', 'pyńdziŏek', 'wtŏrek', 'strzŏda', 'sztwortek', 'pjōntek', 'sobŏta'], + 'weekdays_short' => ['niy', 'pyń', 'wtŏ', 'str', 'szt', 'pjō', 'sob'], + 'weekdays_min' => ['niy', 'pyń', 'wtŏ', 'str', 'szt', 'pjō', 'sob'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count rok', + 'y' => ':count rok', + 'a_year' => ':count rok', + + 'month' => ':count mjeśůnc', + 'm' => ':count mjeśůnc', + 'a_month' => ':count mjeśůnc', + + 'week' => ':count tydźyń', + 'w' => ':count tydźyń', + 'a_week' => ':count tydźyń', + + 'day' => ':count dźyń', + 'd' => ':count dźyń', + 'a_day' => ':count dźyń', + + 'hour' => ':count godzina', + 'h' => ':count godzina', + 'a_hour' => ':count godzina', + + 'minute' => ':count minuta', + 'min' => ':count minuta', + 'a_minute' => ':count minuta', + + 'second' => ':count sekůnda', + 's' => ':count sekůnda', + 'a_second' => ':count sekůnda', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ta.php b/libraries/Carbon/src/Carbon/Lang/ta.php new file mode 100644 index 00000000000..c9507a66538 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ta.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + * - Satheez + */ +return [ + 'year' => ':count வருடம்|:count ஆண்டுகள்', + 'a_year' => 'ஒரு வருடம்|:count ஆண்டுகள்', + 'y' => ':count வருட.|:count ஆண்.', + 'month' => ':count மாதம்|:count மாதங்கள்', + 'a_month' => 'ஒரு மாதம்|:count மாதங்கள்', + 'm' => ':count மாத.', + 'week' => ':count வாரம்|:count வாரங்கள்', + 'a_week' => 'ஒரு வாரம்|:count வாரங்கள்', + 'w' => ':count வார.', + 'day' => ':count நாள்|:count நாட்கள்', + 'a_day' => 'ஒரு நாள்|:count நாட்கள்', + 'd' => ':count நாள்|:count நாட்.', + 'hour' => ':count மணி நேரம்|:count மணி நேரம்', + 'a_hour' => 'ஒரு மணி நேரம்|:count மணி நேரம்', + 'h' => ':count மணி.', + 'minute' => ':count நிமிடம்|:count நிமிடங்கள்', + 'a_minute' => 'ஒரு நிமிடம்|:count நிமிடங்கள்', + 'min' => ':count நிமி.', + 'second' => ':count சில விநாடிகள்|:count விநாடிகள்', + 'a_second' => 'ஒரு சில விநாடிகள்|:count விநாடிகள்', + 's' => ':count விநா.', + 'ago' => ':time முன்', + 'from_now' => ':time இல்', + 'before' => ':time முன்', + 'after' => ':time பின்', + 'diff_now' => 'இப்போது', + 'diff_today' => 'இன்று', + 'diff_yesterday' => 'நேற்று', + 'diff_tomorrow' => 'நாளை', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[இன்று] LT', + 'nextDay' => '[நாளை] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[நேற்று] LT', + 'lastWeek' => '[கடந்த வாரம்] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberவது', + 'meridiem' => function ($hour) { + if ($hour < 2) { + return ' யாமம்'; + } + if ($hour < 6) { + return ' வைகறை'; + } + if ($hour < 10) { + return ' காலை'; + } + if ($hour < 14) { + return ' நண்பகல்'; + } + if ($hour < 18) { + return ' எற்பாடு'; + } + if ($hour < 22) { + return ' மாலை'; + } + + return ' யாமம்'; + }, + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டெம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டெம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'weekdays' => ['ஞாயிற்றுக்கிழமை', 'திங்கட்கிழமை', 'செவ்வாய்கிழமை', 'புதன்கிழமை', 'வியாழக்கிழமை', 'வெள்ளிக்கிழமை', 'சனிக்கிழமை'], + 'weekdays_short' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' மற்றும் '], + 'weekend' => [0, 0], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ta_IN.php b/libraries/Carbon/src/Carbon/Lang/ta_IN.php new file mode 100644 index 00000000000..f3f1d34f3af --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ta_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['காலை', 'மாலை'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ta_LK.php b/libraries/Carbon/src/Carbon/Lang/ta_LK.php new file mode 100644 index 00000000000..bb621fac340 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ta_LK.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - J.Yogaraj 94-777-315206 yogaraj.ubuntu@gmail.com + */ +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன', 'பிப்', 'மார்', 'ஏப்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக', 'செப்', 'அக்', 'நவ', 'டிச'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['காலை', 'மாலை'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ta_MY.php b/libraries/Carbon/src/Carbon/Lang/ta_MY.php new file mode 100644 index 00000000000..a4b14035ebf --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ta_MY.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'D/M/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY, a h:mm', + 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞாயி.', 'திங்.', 'செவ்.', 'புத.', 'வியா.', 'வெள்.', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 1, + 'meridiem' => ['மு.ப', 'பி.ப'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ta_SG.php b/libraries/Carbon/src/Carbon/Lang/ta_SG.php new file mode 100644 index 00000000000..5c32ded92b0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ta_SG.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'D/M/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY, a h:mm', + 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞாயி.', 'திங்.', 'செவ்.', 'புத.', 'வியா.', 'வெள்.', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'meridiem' => ['மு.ப', 'பி.ப'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tcy.php b/libraries/Carbon/src/Carbon/Lang/tcy.php new file mode 100644 index 00000000000..8fb240008c7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tcy.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tcy_IN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/tcy_IN.php b/libraries/Carbon/src/Carbon/Lang/tcy_IN.php new file mode 100644 index 00000000000..016d5a5bb18 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tcy_IN.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IndLinux.org, Samsung Electronics Co., Ltd. alexey.merzlyakov@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ಜನವರಿ', 'ಫೆಬ್ರುವರಿ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್‌‌', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂಬರ್‌', 'ಅಕ್ಟೋಬರ್', 'ನವೆಂಬರ್', 'ಡಿಸೆಂಬರ್'], + 'months_short' => ['ಜ', 'ಫೆ', 'ಮಾ', 'ಏ', 'ಮೇ', 'ಜೂ', 'ಜು', 'ಆ', 'ಸೆ', 'ಅ', 'ನ', 'ಡಿ'], + 'weekdays' => ['ಐಥಾರ', 'ಸೋಮಾರ', 'ಅಂಗರೆ', 'ಬುಧಾರ', 'ಗುರುವಾರ', 'ಶುಕ್ರರ', 'ಶನಿವಾರ'], + 'weekdays_short' => ['ಐ', 'ಸೋ', 'ಅಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'weekdays_min' => ['ಐ', 'ಸೋ', 'ಅಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ಕಾಂಡೆ', 'ಬಯ್ಯ'], + + 'year' => ':count ನೀರ್', // less reliable + 'y' => ':count ನೀರ್', // less reliable + 'a_year' => ':count ನೀರ್', // less reliable + + 'month' => ':count ಮೀನ್', // less reliable + 'm' => ':count ಮೀನ್', // less reliable + 'a_month' => ':count ಮೀನ್', // less reliable + + 'day' => ':count ಸುಗ್ಗಿ', // less reliable + 'd' => ':count ಸುಗ್ಗಿ', // less reliable + 'a_day' => ':count ಸುಗ್ಗಿ', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/te.php b/libraries/Carbon/src/Carbon/Lang/te.php new file mode 100644 index 00000000000..d7eec50d5ad --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/te.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - François B + * - kc + */ +return [ + 'year' => ':count సంవత్సరం|:count సంవత్సరాలు', + 'a_year' => 'ఒక సంవత్సరం|:count సంవత్సరాలు', + 'y' => ':count సం.', + 'month' => ':count నెల|:count నెలలు', + 'a_month' => 'ఒక నెల|:count నెలలు', + 'm' => ':count నెల|:count నెల.', + 'week' => ':count వారం|:count వారాలు', + 'a_week' => 'ఒక వారం|:count వారాలు', + 'w' => ':count వార.|:count వారా.', + 'day' => ':count రోజు|:count రోజులు', + 'a_day' => 'ఒక రోజు|:count రోజులు', + 'd' => ':count రోజు|:count రోజు.', + 'hour' => ':count గంట|:count గంటలు', + 'a_hour' => 'ఒక గంట|:count గంటలు', + 'h' => ':count గం.', + 'minute' => ':count నిమిషం|:count నిమిషాలు', + 'a_minute' => 'ఒక నిమిషం|:count నిమిషాలు', + 'min' => ':count నిమి.', + 'second' => ':count సెకను|:count సెకన్లు', + 'a_second' => 'కొన్ని క్షణాలు|:count సెకన్లు', + 's' => ':count సెక.', + 'ago' => ':time క్రితం', + 'from_now' => ':time లో', + 'diff_now' => 'ప్రస్తుతం', + 'diff_today' => 'నేడు', + 'diff_yesterday' => 'నిన్న', + 'diff_tomorrow' => 'రేపు', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[నేడు] LT', + 'nextDay' => '[రేపు] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[నిన్న] LT', + 'lastWeek' => '[గత] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberవ', + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'రాత్రి'; + } + if ($hour < 10) { + return 'ఉదయం'; + } + if ($hour < 17) { + return 'మధ్యాహ్నం'; + } + if ($hour < 20) { + return 'సాయంత్రం'; + } + + return ' రాత్రి'; + }, + 'months' => ['జనవరి', 'ఫిబ్రవరి', 'మార్చి', 'ఏప్రిల్', 'మే', 'జూన్', 'జూలై', 'ఆగస్టు', 'సెప్టెంబర్', 'అక్టోబర్', 'నవంబర్', 'డిసెంబర్'], + 'months_short' => ['జన.', 'ఫిబ్ర.', 'మార్చి', 'ఏప్రి.', 'మే', 'జూన్', 'జూలై', 'ఆగ.', 'సెప్.', 'అక్టో.', 'నవ.', 'డిసె.'], + 'weekdays' => ['ఆదివారం', 'సోమవారం', 'మంగళవారం', 'బుధవారం', 'గురువారం', 'శుక్రవారం', 'శనివారం'], + 'weekdays_short' => ['ఆది', 'సోమ', 'మంగళ', 'బుధ', 'గురు', 'శుక్ర', 'శని'], + 'weekdays_min' => ['ఆ', 'సో', 'మం', 'బు', 'గు', 'శు', 'శ'], + 'list' => ', ', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/te_IN.php b/libraries/Carbon/src/Carbon/Lang/te_IN.php new file mode 100644 index 00000000000..20b771f78d0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/te_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/te.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/teo.php b/libraries/Carbon/src/Carbon/Lang/teo.php new file mode 100644 index 00000000000..77d4415afa5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/teo.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'meridiem' => ['Taparachu', 'Ebongi'], + 'weekdays' => ['Nakaejuma', 'Nakaebarasa', 'Nakaare', 'Nakauni', 'Nakaung’on', 'Nakakany', 'Nakasabiti'], + 'weekdays_short' => ['Jum', 'Bar', 'Aar', 'Uni', 'Ung', 'Kan', 'Sab'], + 'weekdays_min' => ['Jum', 'Bar', 'Aar', 'Uni', 'Ung', 'Kan', 'Sab'], + 'months' => ['Orara', 'Omuk', 'Okwamg’', 'Odung’el', 'Omaruk', 'Omodok’king’ol', 'Ojola', 'Opedel', 'Osokosokoma', 'Otibar', 'Olabor', 'Opoo'], + 'months_short' => ['Rar', 'Muk', 'Kwa', 'Dun', 'Mar', 'Mod', 'Jol', 'Ped', 'Sok', 'Tib', 'Lab', 'Poo'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/teo_KE.php b/libraries/Carbon/src/Carbon/Lang/teo_KE.php new file mode 100644 index 00000000000..4bb1311570e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/teo_KE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/teo.php', [ + 'first_day_of_week' => 0, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tet.php b/libraries/Carbon/src/Carbon/Lang/tet.php new file mode 100644 index 00000000000..2ff0991160b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tet.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Joshua Brooks + * - François B + */ +return [ + 'year' => 'tinan :count', + 'a_year' => '{1}tinan ida|tinan :count', + 'month' => 'fulan :count', + 'a_month' => '{1}fulan ida|fulan :count', + 'week' => 'semana :count', + 'a_week' => '{1}semana ida|semana :count', + 'day' => 'loron :count', + 'a_day' => '{1}loron ida|loron :count', + 'hour' => 'oras :count', + 'a_hour' => '{1}oras ida|oras :count', + 'minute' => 'minutu :count', + 'a_minute' => '{1}minutu ida|minutu :count', + 'second' => 'segundu :count', + 'a_second' => '{1}segundu balun|segundu :count', + 'ago' => ':time liuba', + 'from_now' => 'iha :time', + 'diff_yesterday' => 'Horiseik', + 'diff_yesterday_regexp' => 'Horiseik(?:\\s+iha)?', + 'diff_today' => 'Ohin', + 'diff_today_regexp' => 'Ohin(?:\\s+iha)?', + 'diff_tomorrow' => 'Aban', + 'diff_tomorrow_regexp' => 'Aban(?:\\s+iha)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Ohin iha] LT', + 'nextDay' => '[Aban iha] LT', + 'nextWeek' => 'dddd [iha] LT', + 'lastDay' => '[Horiseik iha] LT', + 'lastWeek' => 'dddd [semana kotuk] [iha] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Janeiru', 'Fevereiru', 'Marsu', 'Abril', 'Maiu', 'Juñu', 'Jullu', 'Agustu', 'Setembru', 'Outubru', 'Novembru', 'Dezembru'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], + 'weekdays' => ['Domingu', 'Segunda', 'Tersa', 'Kuarta', 'Kinta', 'Sesta', 'Sabadu'], + 'weekdays_short' => ['Dom', 'Seg', 'Ters', 'Kua', 'Kint', 'Sest', 'Sab'], + 'weekdays_min' => ['Do', 'Seg', 'Te', 'Ku', 'Ki', 'Ses', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/tg.php b/libraries/Carbon/src/Carbon/Lang/tg.php new file mode 100644 index 00000000000..dfa986a0774 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tg.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Orif N. Jr + */ +return [ + 'year' => '{1}як сол|:count сол', + 'month' => '{1}як моҳ|:count моҳ', + 'week' => '{1}як ҳафта|:count ҳафта', + 'day' => '{1}як рӯз|:count рӯз', + 'hour' => '{1}як соат|:count соат', + 'minute' => '{1}як дақиқа|:count дақиқа', + 'second' => '{1}якчанд сония|:count сония', + 'ago' => ':time пеш', + 'from_now' => 'баъди :time', + 'diff_today' => 'Имрӯз', + 'diff_yesterday' => 'Дирӯз', + 'diff_yesterday_regexp' => 'Дирӯз(?:\\s+соати)?', + 'diff_tomorrow' => 'Пагоҳ', + 'diff_tomorrow_regexp' => 'Пагоҳ(?:\\s+соати)?', + 'diff_today_regexp' => 'Имрӯз(?:\\s+соати)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Имрӯз соати] LT', + 'nextDay' => '[Пагоҳ соати] LT', + 'nextWeek' => 'dddd[и] [ҳафтаи оянда соати] LT', + 'lastDay' => '[Дирӯз соати] LT', + 'lastWeek' => 'dddd[и] [ҳафтаи гузашта соати] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { // special case for zero + return "$number-ıncı"; + } + + static $suffixes = [ + 0 => '-ум', + 1 => '-ум', + 2 => '-юм', + 3 => '-юм', + 4 => '-ум', + 5 => '-ум', + 6 => '-ум', + 7 => '-ум', + 8 => '-ум', + 9 => '-ум', + 10 => '-ум', + 12 => '-ум', + 13 => '-ум', + 20 => '-ум', + 30 => '-юм', + 40 => '-ум', + 50 => '-ум', + 60 => '-ум', + 70 => '-ум', + 80 => '-ум', + 90 => '-ум', + 100 => '-ум', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'шаб'; + } + if ($hour < 11) { + return 'субҳ'; + } + if ($hour < 16) { + return 'рӯз'; + } + if ($hour < 19) { + return 'бегоҳ'; + } + + return 'шаб'; + }, + 'months' => ['январ', 'феврал', 'март', 'апрел', 'май', 'июн', 'июл', 'август', 'сентябр', 'октябр', 'ноябр', 'декабр'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшанбе', 'душанбе', 'сешанбе', 'чоршанбе', 'панҷшанбе', 'ҷумъа', 'шанбе'], + 'weekdays_short' => ['яшб', 'дшб', 'сшб', 'чшб', 'пшб', 'ҷум', 'шнб'], + 'weekdays_min' => ['яш', 'дш', 'сш', 'чш', 'пш', 'ҷм', 'шб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ва '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/tg_TJ.php b/libraries/Carbon/src/Carbon/Lang/tg_TJ.php new file mode 100644 index 00000000000..164f9acaad6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tg_TJ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/tg.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/th.php b/libraries/Carbon/src/Carbon/Lang/th.php new file mode 100644 index 00000000000..f83d8037663 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/th.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Nate Whittaker + * - John MacAslan + * - Chanintorn Asavavichairoj + * - JD Isaacks + * - ROKAISAKKON + * - RO'KAISAKKON + * - Andreas Möller + * - nithisa + */ +return [ + 'year' => ':count ปี', + 'y' => ':count ปี', + 'month' => ':count เดือน', + 'm' => ':count เดือน', + 'week' => ':count สัปดาห์', + 'w' => ':count สัปดาห์', + 'day' => ':count วัน', + 'd' => ':count วัน', + 'hour' => ':count ชั่วโมง', + 'h' => ':count ชั่วโมง', + 'minute' => ':count นาที', + 'min' => ':count นาที', + 'second' => ':count วินาที', + 'a_second' => '{1}ไม่กี่วินาที|]1,Inf[:count วินาที', + 's' => ':count วินาที', + 'ago' => ':timeที่แล้ว', + 'from_now' => 'อีก :time', + 'after' => ':timeหลังจากนี้', + 'before' => ':timeก่อน', + 'diff_now' => 'ขณะนี้', + 'diff_today' => 'วันนี้', + 'diff_today_regexp' => 'วันนี้(?:\\s+เวลา)?', + 'diff_yesterday' => 'เมื่อวาน', + 'diff_yesterday_regexp' => 'เมื่อวานนี้(?:\\s+เวลา)?', + 'diff_tomorrow' => 'พรุ่งนี้', + 'diff_tomorrow_regexp' => 'พรุ่งนี้(?:\\s+เวลา)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY เวลา H:mm', + 'LLLL' => 'วันddddที่ D MMMM YYYY เวลา H:mm', + ], + 'calendar' => [ + 'sameDay' => '[วันนี้ เวลา] LT', + 'nextDay' => '[พรุ่งนี้ เวลา] LT', + 'nextWeek' => 'dddd[หน้า เวลา] LT', + 'lastDay' => '[เมื่อวานนี้ เวลา] LT', + 'lastWeek' => '[วัน]dddd[ที่แล้ว เวลา] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ก่อนเที่ยง', 'หลังเที่ยง'], + 'months' => ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'], + 'months_short' => ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'], + 'weekdays' => ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัสบดี', 'ศุกร์', 'เสาร์'], + 'weekdays_short' => ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัส', 'ศุกร์', 'เสาร์'], + 'weekdays_min' => ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'], + 'list' => [', ', ' และ '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/th_TH.php b/libraries/Carbon/src/Carbon/Lang/th_TH.php new file mode 100644 index 00000000000..0a7e20ec1da --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/th_TH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/th.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/the.php b/libraries/Carbon/src/Carbon/Lang/the.php new file mode 100644 index 00000000000..5ca5a413502 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/the.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/the_NP.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/the_NP.php b/libraries/Carbon/src/Carbon/Lang/the_NP.php new file mode 100644 index 00000000000..5f6af069554 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/the_NP.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Chitwanix OS Development info@chitwanix.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['आइतबार', 'सोमबार', 'मंगलबार', 'बुधबार', 'बिहिबार', 'शुक्रबार', 'शनिबार'], + 'weekdays_short' => ['आइत', 'सोम', 'मंगल', 'बुध', 'बिहि', 'शुक्र', 'शनि'], + 'weekdays_min' => ['आइत', 'सोम', 'मंगल', 'बुध', 'बिहि', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ti.php b/libraries/Carbon/src/Carbon/Lang/ti.php new file mode 100644 index 00000000000..b34a632e22b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ti.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ti_ER.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ti_ER.php b/libraries/Carbon/src/Carbon/Lang/ti_ER.php new file mode 100644 index 00000000000..43ad14cc15f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ti_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጥሪ', 'ለካቲት', 'መጋቢት', 'ሚያዝያ', 'ግንቦት', 'ሰነ', 'ሓምለ', 'ነሓሰ', 'መስከረም', 'ጥቅምቲ', 'ሕዳር', 'ታሕሳስ'], + 'months_short' => ['ጥሪ ', 'ለካቲ', 'መጋቢ', 'ሚያዝ', 'ግንቦ', 'ሰነ ', 'ሓምለ', 'ነሓሰ', 'መስከ', 'ጥቅም', 'ሕዳር', 'ታሕሳ'], + 'weekdays' => ['ሰንበት', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_short' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_min' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ንጉሆ ሰዓተ', 'ድሕር ሰዓት'], + + 'year' => ':count ዓመት', + 'y' => ':count ዓመት', + 'a_year' => ':count ዓመት', + + 'month' => 'ወርሒ :count', + 'm' => 'ወርሒ :count', + 'a_month' => 'ወርሒ :count', + + 'week' => ':count ሰሙን', + 'w' => ':count ሰሙን', + 'a_week' => ':count ሰሙን', + + 'day' => ':count መዓልቲ', + 'd' => ':count መዓልቲ', + 'a_day' => ':count መዓልቲ', + + 'hour' => ':count ሰዓት', + 'h' => ':count ሰዓት', + 'a_hour' => ':count ሰዓት', + + 'minute' => ':count ደቒቕ', + 'min' => ':count ደቒቕ', + 'a_minute' => ':count ደቒቕ', + + 'second' => ':count ሰከንድ', + 's' => ':count ሰከንድ', + 'a_second' => ':count ሰከንድ', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ti_ET.php b/libraries/Carbon/src/Carbon/Lang/ti_ET.php new file mode 100644 index 00000000000..48dc881da0d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ti_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['ሰንበት', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_short' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_min' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ንጉሆ ሰዓተ', 'ድሕር ሰዓት'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tig.php b/libraries/Carbon/src/Carbon/Lang/tig.php new file mode 100644 index 00000000000..6cca400cd31 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tig.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tig_ER.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/tig_ER.php b/libraries/Carbon/src/Carbon/Lang/tig_ER.php new file mode 100644 index 00000000000..94c3733fbe2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tig_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጥሪ', 'ለካቲት', 'መጋቢት', 'ሚያዝያ', 'ግንቦት', 'ሰነ', 'ሓምለ', 'ነሓሰ', 'መስከረም', 'ጥቅምቲ', 'ሕዳር', 'ታሕሳስ'], + 'months_short' => ['ጥሪ ', 'ለካቲ', 'መጋቢ', 'ሚያዝ', 'ግንቦ', 'ሰነ ', 'ሓምለ', 'ነሓሰ', 'መስከ', 'ጥቅም', 'ሕዳር', 'ታሕሳ'], + 'weekdays' => ['ሰንበት ዓባይ', 'ሰኖ', 'ታላሸኖ', 'ኣረርባዓ', 'ከሚሽ', 'ጅምዓት', 'ሰንበት ንኢሽ'], + 'weekdays_short' => ['ሰ//ዓ', 'ሰኖ ', 'ታላሸ', 'ኣረር', 'ከሚሽ', 'ጅምዓ', 'ሰ//ን'], + 'weekdays_min' => ['ሰ//ዓ', 'ሰኖ ', 'ታላሸ', 'ኣረር', 'ከሚሽ', 'ጅምዓ', 'ሰ//ን'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ቀደም ሰር ምዕል', 'ሓቆ ሰር ምዕል'], + + 'year' => ':count ማይ', // less reliable + 'y' => ':count ማይ', // less reliable + 'a_year' => ':count ማይ', // less reliable + + 'month' => ':count ሸምሽ', // less reliable + 'm' => ':count ሸምሽ', // less reliable + 'a_month' => ':count ሸምሽ', // less reliable + + 'week' => ':count ሰቡዕ', // less reliable + 'w' => ':count ሰቡዕ', // less reliable + 'a_week' => ':count ሰቡዕ', // less reliable + + 'day' => ':count ዎሮ', // less reliable + 'd' => ':count ዎሮ', // less reliable + 'a_day' => ':count ዎሮ', // less reliable + + 'hour' => ':count ሰዓት', // less reliable + 'h' => ':count ሰዓት', // less reliable + 'a_hour' => ':count ሰዓት', // less reliable + + 'minute' => ':count ካልኣይት', // less reliable + 'min' => ':count ካልኣይት', // less reliable + 'a_minute' => ':count ካልኣይት', // less reliable + + 'second' => ':count ካልኣይ', + 's' => ':count ካልኣይ', + 'a_second' => ':count ካልኣይ', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tk.php b/libraries/Carbon/src/Carbon/Lang/tk.php new file mode 100644 index 00000000000..63bfc99ccdb --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tk.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tk_TM.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/tk_TM.php b/libraries/Carbon/src/Carbon/Lang/tk_TM.php new file mode 100644 index 00000000000..3bc481543cd --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tk_TM.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Ghorban M. Tavakoly Pablo Saratxaga & Ghorban M. Tavakoly pablo@walon.org & gmt314@yahoo.com + * - SuperManPHP + * - Maksat Meredow (isadma) + */ +$transformDiff = function ($input) { + return strtr($input, [ + 'sekunt' => 'sekunt', + 'hepde' => 'hepde', + ]); +}; + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Ýanwar', 'Fewral', 'Mart', 'Aprel', 'Maý', 'Iýun', 'Iýul', 'Awgust', 'Sentýabr', 'Oktýabr', 'Noýabr', 'Dekabr'], + 'months_short' => ['Ýan', 'Few', 'Mar', 'Apr', 'Maý', 'Iýn', 'Iýl', 'Awg', 'Sen', 'Okt', 'Noý', 'Dek'], + 'weekdays' => ['Duşenbe', 'Sişenbe', 'Çarşenbe', 'Penşenbe', 'Anna', 'Şenbe', 'Ýekşenbe'], + 'weekdays_short' => ['Duş', 'Siş', 'Çar', 'Pen', 'Ann', 'Şen', 'Ýek'], + 'weekdays_min' => ['Du', 'Si', 'Ça', 'Pe', 'An', 'Şe', 'Ýe'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ýyl', + 'y' => ':count ýyl', + 'a_year' => ':count ýyl', + + 'month' => ':count aý', + 'm' => ':count aý', + 'a_month' => ':count aý', + + 'week' => ':count hepde', + 'w' => ':count hepde', + 'a_week' => ':count hepde', + + 'day' => ':count gün', + 'd' => ':count gün', + 'a_day' => ':count gün', + + 'hour' => ':count sagat', + 'h' => ':count sagat', + 'a_hour' => ':count sagat', + + 'minute' => ':count minut', + 'min' => ':count minut', + 'a_minute' => ':count minut', + + 'second' => ':count sekunt', + 's' => ':count sekunt', + 'a_second' => ':count sekunt', + + 'ago' => function ($time) use ($transformDiff) { + return $transformDiff($time).' ozal'; + }, + 'from_now' => function ($time) use ($transformDiff) { + return $transformDiff($time).' soňra'; + }, + 'after' => function ($time) use ($transformDiff) { + return $transformDiff($time).' soň'; + }, + 'before' => function ($time) use ($transformDiff) { + return $transformDiff($time).' öň'; + }, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tl.php b/libraries/Carbon/src/Carbon/Lang/tl.php new file mode 100644 index 00000000000..951d64cb0dc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tl.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => ':count taon', + 'a_year' => '{1}isang taon|:count taon', + 'month' => ':count buwan', + 'a_month' => '{1}isang buwan|:count buwan', + 'week' => ':count linggo', + 'a_week' => '{1}isang linggo|:count linggo', + 'day' => ':count araw', + 'a_day' => '{1}isang araw|:count araw', + 'hour' => ':count oras', + 'a_hour' => '{1}isang oras|:count oras', + 'minute' => ':count minuto', + 'a_minute' => '{1}isang minuto|:count minuto', + 'min' => ':count min.', + 'second' => ':count segundo', + 'a_second' => '{1}ilang segundo|:count segundo', + 's' => ':count seg.', + 'ago' => ':time ang nakalipas', + 'from_now' => 'sa loob ng :time', + 'diff_now' => 'ngayon', + 'diff_today' => 'ngayong', + 'diff_today_regexp' => 'ngayong(?:\\s+araw)?', + 'diff_yesterday' => 'kahapon', + 'diff_tomorrow' => 'bukas', + 'diff_tomorrow_regexp' => 'Bukas(?:\\s+ng)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'MM/D/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY HH:mm', + 'LLLL' => 'dddd, MMMM DD, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => 'LT [ngayong araw]', + 'nextDay' => '[Bukas ng] LT', + 'nextWeek' => 'LT [sa susunod na] dddd', + 'lastDay' => 'LT [kahapon]', + 'lastWeek' => 'LT [noong nakaraang] dddd', + 'sameElse' => 'L', + ], + 'months' => ['Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre'], + 'months_short' => ['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'], + 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkules', 'Huwebes', 'Biyernes', 'Sabado'], + 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'weekdays_min' => ['Li', 'Lu', 'Ma', 'Mi', 'Hu', 'Bi', 'Sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' at '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/tl_PH.php b/libraries/Carbon/src/Carbon/Lang/tl_PH.php new file mode 100644 index 00000000000..3f5024a8349 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tl_PH.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Ian De La Cruz + * - JD Isaacks + */ +return require __DIR__.'/tl.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/tlh.php b/libraries/Carbon/src/Carbon/Lang/tlh.php new file mode 100644 index 00000000000..6051e31aadf --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tlh.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - Dominika + */ +return [ + 'year' => '{1}wa’ DIS|:count DIS', + 'month' => '{1}wa’ jar|:count jar', + 'week' => '{1}wa’ hogh|:count hogh', + 'day' => '{1}wa’ jaj|:count jaj', + 'hour' => '{1}wa’ rep|:count rep', + 'minute' => '{1}wa’ tup|:count tup', + 'second' => '{1}puS lup|:count lup', + 'ago' => function ($time) { + $output = strtr($time, [ + 'jaj' => 'Hu’', + 'jar' => 'wen', + 'DIS' => 'ben', + ]); + + return $output === $time ? "$time ret" : $output; + }, + 'from_now' => function ($time) { + $output = strtr($time, [ + 'jaj' => 'leS', + 'jar' => 'waQ', + 'DIS' => 'nem', + ]); + + return $output === $time ? "$time pIq" : $output; + }, + 'diff_yesterday' => 'wa’Hu’', + 'diff_today' => 'DaHjaj', + 'diff_tomorrow' => 'wa’leS', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[DaHjaj] LT', + 'nextDay' => '[wa’leS] LT', + 'nextWeek' => 'LLL', + 'lastDay' => '[wa’Hu’] LT', + 'lastWeek' => 'LLL', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['tera’ jar wa’', 'tera’ jar cha’', 'tera’ jar wej', 'tera’ jar loS', 'tera’ jar vagh', 'tera’ jar jav', 'tera’ jar Soch', 'tera’ jar chorgh', 'tera’ jar Hut', 'tera’ jar wa’maH', 'tera’ jar wa’maH wa’', 'tera’ jar wa’maH cha’'], + 'months_short' => ['jar wa’', 'jar cha’', 'jar wej', 'jar loS', 'jar vagh', 'jar jav', 'jar Soch', 'jar chorgh', 'jar Hut', 'jar wa’maH', 'jar wa’maH wa’', 'jar wa’maH cha’'], + 'weekdays' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'weekdays_short' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'weekdays_min' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ’ej '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/tn.php b/libraries/Carbon/src/Carbon/Lang/tn.php new file mode 100644 index 00000000000..0b764226a64 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tn_ZA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/tn_ZA.php b/libraries/Carbon/src/Carbon/Lang/tn_ZA.php new file mode 100644 index 00000000000..46ada8555c7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tn_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Ferikgong', 'Tlhakole', 'Mopitlwe', 'Moranang', 'Motsheganong', 'Seetebosigo', 'Phukwi', 'Phatwe', 'Lwetse', 'Diphalane', 'Ngwanatsele', 'Sedimonthole'], + 'months_short' => ['Fer', 'Tlh', 'Mop', 'Mor', 'Mot', 'See', 'Phu', 'Pha', 'Lwe', 'Dip', 'Ngw', 'Sed'], + 'weekdays' => ['laTshipi', 'Mosupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labotlhano', 'Lamatlhatso'], + 'weekdays_short' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'weekdays_min' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'dingwaga di le :count', + 'y' => 'dingwaga di le :count', + 'a_year' => 'dingwaga di le :count', + + 'month' => 'dikgwedi di le :count', + 'm' => 'dikgwedi di le :count', + 'a_month' => 'dikgwedi di le :count', + + 'week' => 'dibeke di le :count', + 'w' => 'dibeke di le :count', + 'a_week' => 'dibeke di le :count', + + 'day' => 'malatsi :count', + 'd' => 'malatsi :count', + 'a_day' => 'malatsi :count', + + 'hour' => 'diura di le :count', + 'h' => 'diura di le :count', + 'a_hour' => 'diura di le :count', + + 'minute' => 'metsotso e le :count', + 'min' => 'metsotso e le :count', + 'a_minute' => 'metsotso e le :count', + + 'second' => 'metsotswana e le :count', + 's' => 'metsotswana e le :count', + 'a_second' => 'metsotswana e le :count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/to.php b/libraries/Carbon/src/Carbon/Lang/to.php new file mode 100644 index 00000000000..02cb4faed66 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/to.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/to_TO.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/to_TO.php b/libraries/Carbon/src/Carbon/Lang/to_TO.php new file mode 100644 index 00000000000..35ff90addc0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/to_TO.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - International Components for Unicode akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['Sānuali', 'Fēpueli', 'Maʻasi', 'ʻEpeleli', 'Mē', 'Sune', 'Siulai', 'ʻAokosi', 'Sepitema', 'ʻOkatopa', 'Nōvema', 'Tīsema'], + 'months_short' => ['Sān', 'Fēp', 'Maʻa', 'ʻEpe', 'Mē', 'Sun', 'Siu', 'ʻAok', 'Sep', 'ʻOka', 'Nōv', 'Tīs'], + 'weekdays' => ['Sāpate', 'Mōnite', 'Tūsite', 'Pulelulu', 'Tuʻapulelulu', 'Falaite', 'Tokonaki'], + 'weekdays_short' => ['Sāp', 'Mōn', 'Tūs', 'Pul', 'Tuʻa', 'Fal', 'Tok'], + 'weekdays_min' => ['Sāp', 'Mōn', 'Tūs', 'Pul', 'Tuʻa', 'Fal', 'Tok'], + 'meridiem' => ['hengihengi', 'efiafi'], + + 'year' => ':count fitu', // less reliable + 'y' => ':count fitu', // less reliable + 'a_year' => ':count fitu', // less reliable + + 'month' => ':count mahina', // less reliable + 'm' => ':count mahina', // less reliable + 'a_month' => ':count mahina', // less reliable + + 'week' => ':count Sapate', // less reliable + 'w' => ':count Sapate', // less reliable + 'a_week' => ':count Sapate', // less reliable + + 'day' => ':count ʻaho', // less reliable + 'd' => ':count ʻaho', // less reliable + 'a_day' => ':count ʻaho', // less reliable + + 'hour' => ':count houa', + 'h' => ':count houa', + 'a_hour' => ':count houa', + + 'minute' => ':count miniti', + 'min' => ':count miniti', + 'a_minute' => ':count miniti', + + 'second' => ':count sekoni', + 's' => ':count sekoni', + 'a_second' => ':count sekoni', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tpi.php b/libraries/Carbon/src/Carbon/Lang/tpi.php new file mode 100644 index 00000000000..4ddec3ab85d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tpi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tpi_PG.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/tpi_PG.php b/libraries/Carbon/src/Carbon/Lang/tpi_PG.php new file mode 100644 index 00000000000..689c4609376 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tpi_PG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janueri', 'Februeri', 'Mas', 'Epril', 'Me', 'Jun', 'Julai', 'Ogas', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mas', 'Epr', 'Me', 'Jun', 'Jul', 'Oga', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Sande', 'Mande', 'Tunde', 'Trinde', 'Fonde', 'Fraide', 'Sarere'], + 'weekdays_short' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'weekdays_min' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['biknait', 'apinun'], + + 'year' => 'yia :count', + 'y' => 'yia :count', + 'a_year' => 'yia :count', + + 'month' => ':count mun', + 'm' => ':count mun', + 'a_month' => ':count mun', + + 'week' => ':count wik', + 'w' => ':count wik', + 'a_week' => ':count wik', + + 'day' => ':count de', + 'd' => ':count de', + 'a_day' => ':count de', + + 'hour' => ':count aua', + 'h' => ':count aua', + 'a_hour' => ':count aua', + + 'minute' => ':count minit', + 'min' => ':count minit', + 'a_minute' => ':count minit', + + 'second' => ':count namba tu', + 's' => ':count namba tu', + 'a_second' => ':count namba tu', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tr.php b/libraries/Carbon/src/Carbon/Lang/tr.php new file mode 100644 index 00000000000..02f33f19e22 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tr.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Alan Agius + * - Erhan Gundogan + * - François B + * - JD Isaacks + * - Murat Yüksel + * - Baran Şengül + * - Selami (selamialtin) + * - TeomanBey + */ +return [ + 'year' => ':count yıl', + 'a_year' => '{1}bir yıl|]1,Inf[:count yıl', + 'y' => ':county', + 'month' => ':count ay', + 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'm' => ':countay', + 'week' => ':count hafta', + 'a_week' => '{1}bir hafta|]1,Inf[:count hafta', + 'w' => ':counth', + 'day' => ':count gün', + 'a_day' => '{1}bir gün|]1,Inf[:count gün', + 'd' => ':countg', + 'hour' => ':count saat', + 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'h' => ':countsa', + 'minute' => ':count dakika', + 'a_minute' => '{1}bir dakika|]1,Inf[:count dakika', + 'min' => ':countdk', + 'second' => ':count saniye', + 'a_second' => '{1}birkaç saniye|]1,Inf[:count saniye', + 's' => ':countsn', + 'ago' => ':time önce', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time önce', + 'diff_now' => 'şimdi', + 'diff_today' => 'bugün', + 'diff_today_regexp' => 'bugün(?:\\s+saat)?', + 'diff_yesterday' => 'dün', + 'diff_tomorrow' => 'yarın', + 'diff_tomorrow_regexp' => 'yarın(?:\\s+saat)?', + 'diff_before_yesterday' => 'evvelsi gün', + 'diff_after_tomorrow' => 'öbür gün', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[bugün saat] LT', + 'nextDay' => '[yarın saat] LT', + 'nextWeek' => '[gelecek] dddd [saat] LT', + 'lastDay' => '[dün] LT', + 'lastWeek' => '[geçen] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'Do': + case 'DD': + return $number; + default: + if ($number === 0) { // special case for zero + return "$number'ıncı"; + } + + static $suffixes = [ + 1 => '\'inci', + 5 => '\'inci', + 8 => '\'inci', + 70 => '\'inci', + 80 => '\'inci', + 2 => '\'nci', + 7 => '\'nci', + 20 => '\'nci', + 50 => '\'nci', + 3 => '\'üncü', + 4 => '\'üncü', + 100 => '\'üncü', + 6 => '\'ncı', + 9 => '\'uncu', + 10 => '\'uncu', + 30 => '\'uncu', + 60 => '\'ıncı', + 90 => '\'ıncı', + ]; + + $lastDigit = $number % 10; + + return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + } + }, + 'meridiem' => ['ÖÖ', 'ÖS', 'öö', 'ös'], + 'months' => ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'], + 'months_short' => ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'], + 'weekdays' => ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'], + 'weekdays_short' => ['Paz', 'Pts', 'Sal', 'Çar', 'Per', 'Cum', 'Cts'], + 'weekdays_min' => ['Pz', 'Pt', 'Sa', 'Ça', 'Pe', 'Cu', 'Ct'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ve '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/tr_CY.php b/libraries/Carbon/src/Carbon/Lang/tr_CY.php new file mode 100644 index 00000000000..1536c23816b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tr_CY.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/tr.php', [ + 'weekdays_short' => ['Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt'], + 'weekdays_min' => ['Pa', 'Pt', 'Sa', 'Ça', 'Pe', 'Cu', 'Ct'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'D MMMM YYYY dddd h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tr_TR.php b/libraries/Carbon/src/Carbon/Lang/tr_TR.php new file mode 100644 index 00000000000..ca503ad1816 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tr_TR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/tr.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ts.php b/libraries/Carbon/src/Carbon/Lang/ts.php new file mode 100644 index 00000000000..271d67f1195 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ts.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ts_ZA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ts_ZA.php b/libraries/Carbon/src/Carbon/Lang/ts_ZA.php new file mode 100644 index 00000000000..7635551e582 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ts_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Sunguti', 'Nyenyenyani', 'Nyenyankulu', 'Dzivamisoko', 'Mudyaxihi', 'Khotavuxika', 'Mawuwani', 'Mhawuri', 'Ndzhati', 'Nhlangula', 'Hukuri', 'N\'wendzamhala'], + 'months_short' => ['Sun', 'Yan', 'Kul', 'Dzi', 'Mud', 'Kho', 'Maw', 'Mha', 'Ndz', 'Nhl', 'Huk', 'N\'w'], + 'weekdays' => ['Sonto', 'Musumbhunuku', 'Ravumbirhi', 'Ravunharhu', 'Ravumune', 'Ravuntlhanu', 'Mugqivela'], + 'weekdays_short' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'weekdays_min' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'malembe ya :count', + 'y' => 'malembe ya :count', + 'a_year' => 'malembe ya :count', + + 'month' => 'tin’hweti ta :count', + 'm' => 'tin’hweti ta :count', + 'a_month' => 'tin’hweti ta :count', + + 'week' => 'mavhiki ya :count', + 'w' => 'mavhiki ya :count', + 'a_week' => 'mavhiki ya :count', + + 'day' => 'masiku :count', + 'd' => 'masiku :count', + 'a_day' => 'masiku :count', + + 'hour' => 'tiawara ta :count', + 'h' => 'tiawara ta :count', + 'a_hour' => 'tiawara ta :count', + + 'minute' => 'timinete ta :count', + 'min' => 'timinete ta :count', + 'a_minute' => 'timinete ta :count', + + 'second' => 'tisekoni ta :count', + 's' => 'tisekoni ta :count', + 'a_second' => 'tisekoni ta :count', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tt.php b/libraries/Carbon/src/Carbon/Lang/tt.php new file mode 100644 index 00000000000..b332748771c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tt.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tt_RU.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/tt_RU.php b/libraries/Carbon/src/Carbon/Lang/tt_RU.php new file mode 100644 index 00000000000..c684dc4a05a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tt_RU.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rinat Norkin Pablo Saratxaga, Rinat Norkin pablo@mandrakesoft.com, rinat@taif.ru + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'DD MMM, HH:mm', + 'LLLL' => 'DD MMMM YYYY, HH:mm', + ], + 'months' => ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшәмбе', 'дышәмбе', 'сишәмбе', 'чәршәәмбе', 'пәнҗешмбе', 'җомга', 'шимбә'], + 'weekdays_short' => ['якш', 'дыш', 'сиш', 'чәрш', 'пәнҗ', 'җом', 'шим'], + 'weekdays_min' => ['якш', 'дыш', 'сиш', 'чәрш', 'пәнҗ', 'җом', 'шим'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count ел', + 'month' => ':count ай', + 'week' => ':count атна', + 'day' => ':count көн', + 'hour' => ':count сәгать', + 'minute' => ':count минут', + 'second' => ':count секунд', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/tt_RU@iqtelif.php b/libraries/Carbon/src/Carbon/Lang/tt_RU@iqtelif.php new file mode 100644 index 00000000000..b1494447d28 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tt_RU@iqtelif.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Reshat Sabiq tatar.iqtelif.i18n@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Ğınwar', 'Fiwral\'', 'Mart', 'April', 'May', 'Yün', 'Yül', 'Awgust', 'Sintebír', 'Üktebír', 'Noyebír', 'Dikebír'], + 'months_short' => ['Ğın', 'Fiw', 'Mar', 'Apr', 'May', 'Yün', 'Yül', 'Awg', 'Sin', 'Ükt', 'Noy', 'Dik'], + 'weekdays' => ['Yekşembí', 'Düşembí', 'Sişembí', 'Çerşembí', 'Pencíşembí', 'Comğa', 'Şimbe'], + 'weekdays_short' => ['Yek', 'Düş', 'Siş', 'Çer', 'Pen', 'Com', 'Şim'], + 'weekdays_min' => ['Yek', 'Düş', 'Siş', 'Çer', 'Pen', 'Com', 'Şim'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ÖA', 'ÖS'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/twq.php b/libraries/Carbon/src/Carbon/Lang/twq.php new file mode 100644 index 00000000000..bcea22687af --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/twq.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ses.php', [ + 'meridiem' => ['Subbaahi', 'Zaarikay b'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/tzl.php b/libraries/Carbon/src/Carbon/Lang/tzl.php new file mode 100644 index 00000000000..7487b902107 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tzl.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => '[0,1]:count ar|:count ars', + 'y' => '[0,1]:count ar|:count ars', + 'month' => '[0,1]:count mes|:count mesen', + 'm' => '[0,1]:count mes|:count mesen', + 'week' => '[0,1]:count seifetziua|:count seifetziuas', + 'w' => '[0,1]:count seifetziua|:count seifetziuas', + 'day' => '[0,1]:count ziua|:count ziuas', + 'd' => '[0,1]:count ziua|:count ziuas', + 'hour' => '[0,1]:count þora|:count þoras', + 'h' => '[0,1]:count þora|:count þoras', + 'minute' => '[0,1]:count míut|:count míuts', + 'min' => '[0,1]:count míut|:count míuts', + 'second' => ':count secunds', + 's' => ':count secunds', + + 'ago' => 'ja :time', + 'from_now' => 'osprei :time', + + 'diff_yesterday' => 'ieiri', + 'diff_yesterday_regexp' => 'ieiri(?:\\s+à)?', + 'diff_today' => 'oxhi', + 'diff_today_regexp' => 'oxhi(?:\\s+à)?', + 'diff_tomorrow' => 'demà', + 'diff_tomorrow_regexp' => 'demà(?:\\s+à)?', + + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM [dallas] YYYY', + 'LLL' => 'D. MMMM [dallas] YYYY HH.mm', + 'LLLL' => 'dddd, [li] D. MMMM [dallas] YYYY HH.mm', + ], + + 'calendar' => [ + 'sameDay' => '[oxhi à] LT', + 'nextDay' => '[demà à] LT', + 'nextWeek' => 'dddd [à] LT', + 'lastDay' => '[ieiri à] LT', + 'lastWeek' => '[sür el] dddd [lasteu à] LT', + 'sameElse' => 'L', + ], + + 'meridiem' => ["D'A", "D'O"], + 'months' => ['Januar', 'Fevraglh', 'Març', 'Avrïu', 'Mai', 'Gün', 'Julia', 'Guscht', 'Setemvar', 'Listopäts', 'Noemvar', 'Zecemvar'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Gün', 'Jul', 'Gus', 'Set', 'Lis', 'Noe', 'Zec'], + 'weekdays' => ['Súladi', 'Lúneçi', 'Maitzi', 'Márcuri', 'Xhúadi', 'Viénerçi', 'Sáturi'], + 'weekdays_short' => ['Súl', 'Lún', 'Mai', 'Már', 'Xhú', 'Vié', 'Sát'], + 'weekdays_min' => ['Sú', 'Lú', 'Ma', 'Má', 'Xh', 'Vi', 'Sá'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/tzm.php b/libraries/Carbon/src/Carbon/Lang/tzm.php new file mode 100644 index 00000000000..1444d549753 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tzm.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}ⴰⵙⴳⴰⵙ|:count ⵉⵙⴳⴰⵙⵏ', + 'month' => '{1}ⴰⵢoⵓⵔ|:count ⵉⵢⵢⵉⵔⵏ', + 'week' => ':count ⵉⵎⴰⵍⴰⵙⵙ', + 'day' => '{1}ⴰⵙⵙ|:count oⵙⵙⴰⵏ', + 'hour' => '{1}ⵙⴰⵄⴰ|:count ⵜⴰⵙⵙⴰⵄⵉⵏ', + 'minute' => '{1}ⵎⵉⵏⵓⴺ|:count ⵎⵉⵏⵓⴺ', + 'second' => '{1}ⵉⵎⵉⴽ|:count ⵉⵎⵉⴽ', + 'ago' => 'ⵢⴰⵏ :time', + 'from_now' => 'ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ :time', + 'diff_today' => 'ⴰⵙⴷⵅ', + 'diff_yesterday' => 'ⴰⵚⴰⵏⵜ', + 'diff_yesterday_regexp' => 'ⴰⵚⴰⵏⵜ(?:\\s+ⴴ)?', + 'diff_tomorrow' => 'ⴰⵙⴽⴰ', + 'diff_tomorrow_regexp' => 'ⴰⵙⴽⴰ(?:\\s+ⴴ)?', + 'diff_today_regexp' => 'ⴰⵙⴷⵅ(?:\\s+ⴴ)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ⴰⵙⴷⵅ ⴴ] LT', + 'nextDay' => '[ⴰⵙⴽⴰ ⴴ] LT', + 'nextWeek' => 'dddd [ⴴ] LT', + 'lastDay' => '[ⴰⵚⴰⵏⵜ ⴴ] LT', + 'lastWeek' => 'dddd [ⴴ] LT', + 'sameElse' => 'L', + ], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⵏⴱⵉⵔ'], + 'weekdays' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_min' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'weekend' => [5, 6], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/tzm_Latn.php b/libraries/Carbon/src/Carbon/Lang/tzm_Latn.php new file mode 100644 index 00000000000..0dea96cded2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/tzm_Latn.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}:count asgas|:count isgasn', + 'a_year' => 'asgas|:count isgasn', + 'month' => '{1}:count ayowr|:count iyyirn', + 'a_month' => 'ayowr|:count iyyirn', + 'week' => ':count imalass', + 'a_week' => ':imalass', + 'day' => '{1}:count ass|:count ossan', + 'a_day' => 'ass|:count ossan', + 'hour' => '{1}:count saɛa|:count tassaɛin', + 'a_hour' => '{1}saɛa|:count tassaɛin', + 'minute' => ':count minuḍ', + 'a_minute' => '{1}minuḍ|:count minuḍ', + 'second' => ':count imik', + 'a_second' => '{1}imik|:count imik', + 'ago' => 'yan :time', + 'from_now' => 'dadkh s yan :time', + 'diff_yesterday' => 'assant', + 'diff_yesterday_regexp' => 'assant(?:\\s+g)?', + 'diff_today' => 'asdkh', + 'diff_today_regexp' => 'asdkh(?:\\s+g)?', + 'diff_tomorrow' => 'aska', + 'diff_tomorrow_regexp' => 'aska(?:\\s+g)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[asdkh g] LT', + 'nextDay' => '[aska g] LT', + 'nextWeek' => 'dddd [g] LT', + 'lastDay' => '[assant g] LT', + 'lastWeek' => 'dddd [g] LT', + 'sameElse' => 'L', + ], + 'months' => ['innayr', 'brˤayrˤ', 'marˤsˤ', 'ibrir', 'mayyw', 'ywnyw', 'ywlywz', 'ɣwšt', 'šwtanbir', 'ktˤwbrˤ', 'nwwanbir', 'dwjnbir'], + 'months_short' => ['innayr', 'brˤayrˤ', 'marˤsˤ', 'ibrir', 'mayyw', 'ywnyw', 'ywlywz', 'ɣwšt', 'šwtanbir', 'ktˤwbrˤ', 'nwwanbir', 'dwjnbir'], + 'weekdays' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_short' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_min' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'meridiem' => ['Zdat azal', 'Ḍeffir aza'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ug.php b/libraries/Carbon/src/Carbon/Lang/ug.php new file mode 100644 index 00000000000..6d58f7fc383 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ug.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - yasinn + */ +return [ + 'year' => '{1}'.'بىر يىل'.'|:count '.'يىل', + 'month' => '{1}'.'بىر ئاي'.'|:count '.'ئاي', + 'week' => '{1}'.'بىر ھەپتە'.'|:count '.'ھەپتە', + 'day' => '{1}'.'بىر كۈن'.'|:count '.'كۈن', + 'hour' => '{1}'.'بىر سائەت'.'|:count '.'سائەت', + 'minute' => '{1}'.'بىر مىنۇت'.'|:count '.'مىنۇت', + 'second' => '{1}'.'نەچچە سېكونت'.'|:count '.'سېكونت', + 'ago' => ':time بۇرۇن', + 'from_now' => ':time كېيىن', + 'diff_today' => 'بۈگۈن', + 'diff_yesterday' => 'تۆنۈگۈن', + 'diff_tomorrow' => 'ئەتە', + 'diff_tomorrow_regexp' => 'ئەتە(?:\\s+سائەت)?', + 'diff_today_regexp' => 'بۈگۈن(?:\\s+سائەت)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY-يىلىM-ئاينىڭD-كۈنى', + 'LLL' => 'YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm', + 'LLLL' => 'dddd، YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[بۈگۈن سائەت] LT', + 'nextDay' => '[ئەتە سائەت] LT', + 'nextWeek' => '[كېلەركى] dddd [سائەت] LT', + 'lastDay' => '[تۆنۈگۈن] LT', + 'lastWeek' => '[ئالدىنقى] dddd [سائەت] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'-كۈنى'; + case 'w': + case 'W': + return $number.'-ھەپتە'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return 'يېرىم كېچە'; + } + if ($time < 900) { + return 'سەھەر'; + } + if ($time < 1130) { + return 'چۈشتىن بۇرۇن'; + } + if ($time < 1230) { + return 'چۈش'; + } + if ($time < 1800) { + return 'چۈشتىن كېيىن'; + } + + return 'كەچ'; + }, + 'months' => ['يانۋار', 'فېۋرال', 'مارت', 'ئاپرېل', 'ماي', 'ئىيۇن', 'ئىيۇل', 'ئاۋغۇست', 'سېنتەبىر', 'ئۆكتەبىر', 'نويابىر', 'دېكابىر'], + 'months_short' => ['يانۋار', 'فېۋرال', 'مارت', 'ئاپرېل', 'ماي', 'ئىيۇن', 'ئىيۇل', 'ئاۋغۇست', 'سېنتەبىر', 'ئۆكتەبىر', 'نويابىر', 'دېكابىر'], + 'weekdays' => ['يەكشەنبە', 'دۈشەنبە', 'سەيشەنبە', 'چارشەنبە', 'پەيشەنبە', 'جۈمە', 'شەنبە'], + 'weekdays_short' => ['يە', 'دۈ', 'سە', 'چا', 'پە', 'جۈ', 'شە'], + 'weekdays_min' => ['يە', 'دۈ', 'سە', 'چا', 'پە', 'جۈ', 'شە'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ۋە '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ug_CN.php b/libraries/Carbon/src/Carbon/Lang/ug_CN.php new file mode 100644 index 00000000000..51108dd2d02 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ug_CN.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Alim Boyaq + */ +return require __DIR__.'/ug.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/uk.php b/libraries/Carbon/src/Carbon/Lang/uk.php new file mode 100644 index 00000000000..748e2950173 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/uk.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Carbon\CarbonInterface; + +$processHoursFunction = function (CarbonInterface $date, string $format) { + return $format.'о'.($date->hour === 11 ? 'б' : '').'] LT'; +}; + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - François B + * - Tim Fish + * - Serhan Apaydın + * - Max Mykhailenko + * - JD Isaacks + * - Max Kovpak + * - AucT + * - Philippe Vaucher + * - Ilya Shaplyko + * - Vadym Ievsieiev + * - Denys Kurets + * - Igor Kasyanchuk + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Oleh + * - epaminond + * - Juanito Fatas + * - Vitalii Khustochka + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Andriy Tyurnikov + * - Nicolás Hock Isaza + * - Iwakura Taro + * - Andrii Ponomarov + * - alecrabbit + * - vystepanenko + * - AlexWalkerson + * - Andre Havryliuk (Andrend) + * - Max Datsenko (datsenko-md) + */ +return [ + 'year' => ':count рік|:count роки|:count років', + 'y' => ':countр|:countрр|:countрр', + 'a_year' => '{1}рік|:count рік|:count роки|:count років', + 'month' => ':count місяць|:count місяці|:count місяців', + 'm' => ':countм', + 'a_month' => '{1}місяць|:count місяць|:count місяці|:count місяців', + 'week' => ':count тиждень|:count тижні|:count тижнів', + 'w' => ':countт', + 'a_week' => '{1}тиждень|:count тиждень|:count тижні|:count тижнів', + 'day' => ':count день|:count дні|:count днів', + 'd' => ':countд', + 'a_day' => '{1}день|:count день|:count дні|:count днів', + 'hour' => ':count година|:count години|:count годин', + 'h' => ':countг', + 'a_hour' => '{1}година|:count година|:count години|:count годин', + 'minute' => ':count хвилина|:count хвилини|:count хвилин', + 'min' => ':countхв', + 'a_minute' => '{1}хвилина|:count хвилина|:count хвилини|:count хвилин', + 'second' => ':count секунда|:count секунди|:count секунд', + 's' => ':countсек', + 'a_second' => '{1}декілька секунд|:count секунда|:count секунди|:count секунд', + + 'hour_ago' => ':count годину|:count години|:count годин', + 'a_hour_ago' => '{1}годину|:count годину|:count години|:count годин', + 'minute_ago' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_ago' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_ago' => ':count секунду|:count секунди|:count секунд', + 'a_second_ago' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_from_now' => ':count годину|:count години|:count годин', + 'a_hour_from_now' => '{1}годину|:count годину|:count години|:count годин', + 'minute_from_now' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_from_now' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_from_now' => ':count секунду|:count секунди|:count секунд', + 'a_second_from_now' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_after' => ':count годину|:count години|:count годин', + 'a_hour_after' => '{1}годину|:count годину|:count години|:count годин', + 'minute_after' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_after' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_after' => ':count секунду|:count секунди|:count секунд', + 'a_second_after' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_before' => ':count годину|:count години|:count годин', + 'a_hour_before' => '{1}годину|:count годину|:count години|:count годин', + 'minute_before' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_before' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_before' => ':count секунду|:count секунди|:count секунд', + 'a_second_before' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'ago' => ':time тому', + 'from_now' => 'за :time', + 'after' => ':time після', + 'before' => ':time до', + 'diff_now' => 'щойно', + 'diff_today' => 'Сьогодні', + 'diff_today_regexp' => 'Сьогодні(?:\\s+о)?', + 'diff_yesterday' => 'вчора', + 'diff_yesterday_regexp' => 'Вчора(?:\\s+о)?', + 'diff_tomorrow' => 'завтра', + 'diff_tomorrow_regexp' => 'Завтра(?:\\s+о)?', + 'diff_before_yesterday' => 'позавчора', + 'diff_after_tomorrow' => 'післязавтра', + 'period_recurrences' => 'один раз|:count рази|:count разів', + 'period_interval' => 'кожні :interval', + 'period_start_date' => 'з :date', + 'period_end_date' => 'до :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Сьогодні '); + }, + 'nextDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Завтра '); + }, + 'nextWeek' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[У] dddd ['); + }, + 'lastDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Вчора '); + }, + 'lastWeek' => function (CarbonInterface $date) use ($processHoursFunction) { + switch ($date->dayOfWeek) { + case 0: + case 3: + case 5: + case 6: + return $processHoursFunction($date, '[Минулої] dddd ['); + default: + return $processHoursFunction($date, '[Минулого] dddd ['); + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return $number.'-й'; + case 'D': + return $number.'-го'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночі'; + } + if ($hour < 12) { + return 'ранку'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечора'; + }, + 'months' => ['січня', 'лютого', 'березня', 'квітня', 'травня', 'червня', 'липня', 'серпня', 'вересня', 'жовтня', 'листопада', 'грудня'], + 'months_standalone' => ['січень', 'лютий', 'березень', 'квітень', 'травень', 'червень', 'липень', 'серпень', 'вересень', 'жовтень', 'листопад', 'грудень'], + 'months_short' => ['січ', 'лют', 'бер', 'кві', 'тра', 'чер', 'лип', 'сер', 'вер', 'жов', 'лис', 'гру'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => function (CarbonInterface $date, $format, $index) { + static $words = [ + 'nominative' => ['неділя', 'понеділок', 'вівторок', 'середа', 'четвер', 'п’ятниця', 'субота'], + 'accusative' => ['неділю', 'понеділок', 'вівторок', 'середу', 'четвер', 'п’ятницю', 'суботу'], + 'genitive' => ['неділі', 'понеділка', 'вівторка', 'середи', 'четверга', 'п’ятниці', 'суботи'], + ]; + + $format = $format ?? ''; + $nounCase = preg_match('/(\[(В|в|У|у)\])\s+dddd/u', $format) + ? 'accusative' + : ( + preg_match('/\[?(?:минулої|наступної)?\s*\]\s+dddd/u', $format) + ? 'genitive' + : 'nominative' + ); + + return $words[$nounCase][$index] ?? null; + }, + 'weekdays_short' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'weekdays_min' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/uk_UA.php b/libraries/Carbon/src/Carbon/Lang/uk_UA.php new file mode 100644 index 00000000000..ab69f14c772 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/uk_UA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/uk.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/unm.php b/libraries/Carbon/src/Carbon/Lang/unm.php new file mode 100644 index 00000000000..fcbddafe37d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/unm.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/unm_US.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/unm_US.php b/libraries/Carbon/src/Carbon/Lang/unm_US.php new file mode 100644 index 00000000000..9984691aeb8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/unm_US.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['enikwsi', 'chkwali', 'xamokhwite', 'kwetayoxe', 'tainipen', 'kichinipen', 'lainipen', 'winaminke', 'kichitahkok', 'puksit', 'wini', 'muxkotae'], + 'months_short' => ['eni', 'chk', 'xam', 'kwe', 'tai', 'nip', 'lai', 'win', 'tah', 'puk', 'kun', 'mux'], + 'weekdays' => ['kentuwei', 'manteke', 'tusteke', 'lelai', 'tasteke', 'pelaiteke', 'sateteke'], + 'weekdays_short' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'weekdays_min' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'day_of_first_week_of_year' => 1, + + // Too unreliable + /* + 'year' => ':count kaxtëne', + 'y' => ':count kaxtëne', + 'a_year' => ':count kaxtëne', + + 'month' => ':count piskewëni kishux', // less reliable + 'm' => ':count piskewëni kishux', // less reliable + 'a_month' => ':count piskewëni kishux', // less reliable + + 'week' => ':count kishku', // less reliable + 'w' => ':count kishku', // less reliable + 'a_week' => ':count kishku', // less reliable + + 'day' => ':count kishku', + 'd' => ':count kishku', + 'a_day' => ':count kishku', + + 'hour' => ':count xkuk', // less reliable + 'h' => ':count xkuk', // less reliable + 'a_hour' => ':count xkuk', // less reliable + + 'minute' => ':count txituwàk', // less reliable + 'min' => ':count txituwàk', // less reliable + 'a_minute' => ':count txituwàk', // less reliable + + 'second' => ':count nisha', // less reliable + 's' => ':count nisha', // less reliable + 'a_second' => ':count nisha', // less reliable + */ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ur.php b/libraries/Carbon/src/Carbon/Lang/ur.php new file mode 100644 index 00000000000..688f61ba758 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ur.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'جنوری', + 'فروری', + 'مارچ', + 'اپریل', + 'مئی', + 'جون', + 'جولائی', + 'اگست', + 'ستمبر', + 'اکتوبر', + 'نومبر', + 'دسمبر', +]; + +$weekdays = [ + 'اتوار', + 'پیر', + 'منگل', + 'بدھ', + 'جمعرات', + 'جمعہ', + 'ہفتہ', +]; + +/* + * Authors: + * - Sawood Alam + * - Mehshan + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Zaid Akram + * - Max Melentiev + * - hafezdivandari + * - Hossein Jabbari + * - nimamo + */ +return [ + 'year' => 'ایک سال|:count سال', + 'month' => 'ایک ماہ|:count ماہ', + 'week' => ':count ہفتے', + 'day' => 'ایک دن|:count دن', + 'hour' => 'ایک گھنٹہ|:count گھنٹے', + 'minute' => 'ایک منٹ|:count منٹ', + 'second' => 'چند سیکنڈ|:count سیکنڈ', + 'ago' => ':time قبل', + 'from_now' => ':time بعد', + 'after' => ':time بعد', + 'before' => ':time پہلے', + 'diff_now' => 'اب', + 'diff_today' => 'آج', + 'diff_today_regexp' => 'آج(?:\\s+بوقت)?', + 'diff_yesterday' => 'گزشتہ کل', + 'diff_yesterday_regexp' => 'گذشتہ(?:\\s+روز)?(?:\\s+بوقت)?', + 'diff_tomorrow' => 'آئندہ کل', + 'diff_tomorrow_regexp' => 'کل(?:\\s+بوقت)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd، D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[آج بوقت] LT', + 'nextDay' => '[کل بوقت] LT', + 'nextWeek' => 'dddd [بوقت] LT', + 'lastDay' => '[گذشتہ روز بوقت] LT', + 'lastWeek' => '[گذشتہ] dddd [بوقت] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['صبح', 'شام'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => ['، ', ' اور '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/ur_IN.php b/libraries/Carbon/src/Carbon/Lang/ur_IN.php new file mode 100644 index 00000000000..91084df7684 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ur_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'weekdays_short' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'weekdays_min' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/ur_PK.php b/libraries/Carbon/src/Carbon/Lang/ur_PK.php new file mode 100644 index 00000000000..4cc18283902 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ur_PK.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_short' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_min' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ص', 'ش'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/uz.php b/libraries/Carbon/src/Carbon/Lang/uz.php new file mode 100644 index 00000000000..6cb93bb97bf --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/uz.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dmitriy Shabanov + * - JD Isaacks + * - Inoyatulloh + * - Jamshid + * - aarkhipov + * - Philippe Vaucher + * - felixthemagnificent + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Alisher Ulugbekov + * - Ergashev Adizbek + */ +return [ + 'year' => ':count йил', + 'a_year' => '{1}бир йил|:count йил', + 'y' => ':count й', + 'month' => ':count ой', + 'a_month' => '{1}бир ой|:count ой', + 'm' => ':count о', + 'week' => ':count ҳафта', + 'a_week' => '{1}бир ҳафта|:count ҳафта', + 'w' => ':count ҳ', + 'day' => ':count кун', + 'a_day' => '{1}бир кун|:count кун', + 'd' => ':count к', + 'hour' => ':count соат', + 'a_hour' => '{1}бир соат|:count соат', + 'h' => ':count с', + 'minute' => ':count дақиқа', + 'a_minute' => '{1}бир дақиқа|:count дақиқа', + 'min' => ':count д', + 'second' => ':count сония', + 'a_second' => '{1}сония|:count сония', + 's' => ':count с', + 'ago' => ':time аввал', + 'from_now' => 'Якин :time ичида', + 'after' => ':timeдан кейин', + 'before' => ':time олдин', + 'diff_now' => 'ҳозир', + 'diff_today' => 'Бугун', + 'diff_today_regexp' => 'Бугун(?:\\s+соат)?', + 'diff_yesterday' => 'Кеча', + 'diff_yesterday_regexp' => 'Кеча(?:\\s+соат)?', + 'diff_tomorrow' => 'Эртага', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бугун соат] LT [да]', + 'nextDay' => '[Эртага] LT [да]', + 'nextWeek' => 'dddd [куни соат] LT [да]', + 'lastDay' => '[Кеча соат] LT [да]', + 'lastWeek' => '[Утган] dddd [куни соат] LT [да]', + 'sameElse' => 'L', + ], + 'months' => ['январ', 'феврал', 'март', 'апрел', 'май', 'июн', 'июл', 'август', 'сентябр', 'октябр', 'ноябр', 'декабр'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшанба', 'душанба', 'сешанба', 'чоршанба', 'пайшанба', 'жума', 'шанба'], + 'weekdays_short' => ['якш', 'душ', 'сеш', 'чор', 'пай', 'жум', 'шан'], + 'weekdays_min' => ['як', 'ду', 'се', 'чо', 'па', 'жу', 'ша'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['эрталаб', 'кечаси'], + 'list' => [', ', ' ва '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/uz_Arab.php b/libraries/Carbon/src/Carbon/Lang/uz_Arab.php new file mode 100644 index 00000000000..84575491c92 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/uz_Arab.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_short' => ['ی.', 'د.', 'س.', 'چ.', 'پ.', 'ج.', 'ش.'], + 'weekdays_min' => ['ی.', 'د.', 'س.', 'چ.', 'پ.', 'ج.', 'ش.'], + 'months' => ['جنوری', 'فبروری', 'مارچ', 'اپریل', 'می', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنو', 'فبر', 'مار', 'اپر', 'می', 'جون', 'جول', 'اگس', 'سپت', 'اکت', 'نوم', 'دسم'], + 'first_day_of_week' => 6, + 'weekend' => [4, 5], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/uz_Cyrl.php b/libraries/Carbon/src/Carbon/Lang/uz_Cyrl.php new file mode 100644 index 00000000000..18d8c223d68 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/uz_Cyrl.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/uz.php', [ + 'formats' => [ + 'L' => 'DD/MM/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, DD MMMM, YYYY HH:mm', + ], + 'meridiem' => ['ТО', 'ТК'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/uz_Latn.php b/libraries/Carbon/src/Carbon/Lang/uz_Latn.php new file mode 100644 index 00000000000..0f1db8a1734 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/uz_Latn.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Rasulbek + * - Ilyosjon Kamoldinov (ilyosjon09) + */ +return [ + 'year' => ':count yil', + 'a_year' => '{1}bir yil|:count yil', + 'y' => ':count y', + 'month' => ':count oy', + 'a_month' => '{1}bir oy|:count oy', + 'm' => ':count o', + 'week' => ':count hafta', + 'a_week' => '{1}bir hafta|:count hafta', + 'w' => ':count h', + 'day' => ':count kun', + 'a_day' => '{1}bir kun|:count kun', + 'd' => ':count k', + 'hour' => ':count soat', + 'a_hour' => '{1}bir soat|:count soat', + 'h' => ':count soat', + 'minute' => ':count daqiqa', + 'a_minute' => '{1}bir daqiqa|:count daqiqa', + 'min' => ':count d', + 'second' => ':count soniya', + 'a_second' => '{1}soniya|:count soniya', + 's' => ':count son.', + 'ago' => ':time avval', + 'from_now' => 'Yaqin :time ichida', + 'after' => ':timedan keyin', + 'before' => ':time oldin', + 'diff_yesterday' => 'Kecha', + 'diff_yesterday_regexp' => 'Kecha(?:\\s+soat)?', + 'diff_today' => 'Bugun', + 'diff_today_regexp' => 'Bugun(?:\\s+soat)?', + 'diff_tomorrow' => 'Ertaga', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Bugun soat] LT [da]', + 'nextDay' => '[Ertaga] LT [da]', + 'nextWeek' => 'dddd [kuni soat] LT [da]', + 'lastDay' => '[Kecha soat] LT [da]', + 'lastWeek' => '[O\'tgan] dddd [kuni soat] LT [da]', + 'sameElse' => 'L', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'Iyun', 'Iyul', 'Avgust', 'Sentabr', 'Oktabr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyun', 'Iyul', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Yakshanba', 'Dushanba', 'Seshanba', 'Chorshanba', 'Payshanba', 'Juma', 'Shanba'], + 'weekdays_short' => ['Yak', 'Dush', 'Sesh', 'Chor', 'Pay', 'Jum', 'Shan'], + 'weekdays_min' => ['Ya', 'Du', 'Se', 'Cho', 'Pa', 'Ju', 'Sha'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' va '], + 'meridiem' => ['TO', 'TK'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/uz_UZ.php b/libraries/Carbon/src/Carbon/Lang/uz_UZ.php new file mode 100644 index 00000000000..d1b1ce009e5 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/uz_UZ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Bobir Ismailov Bobir Ismailov, Pablo Saratxaga, Mashrab Kuvatov bobir_is@yahoo.com, pablo@mandrakesoft.com, kmashrab@uni-bremen.de + */ +return array_replace_recursive(require __DIR__.'/uz_Latn.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'Iyun', 'Iyul', 'Avgust', 'Sentabr', 'Oktabr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyn', 'Iyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Yakshanba', 'Dushanba', 'Seshanba', 'Chorshanba', 'Payshanba', 'Juma', 'Shanba'], + 'weekdays_short' => ['Yak', 'Du', 'Se', 'Cho', 'Pay', 'Ju', 'Sha'], + 'weekdays_min' => ['Yak', 'Du', 'Se', 'Cho', 'Pay', 'Ju', 'Sha'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/uz_UZ@cyrillic.php b/libraries/Carbon/src/Carbon/Lang/uz_UZ@cyrillic.php new file mode 100644 index 00000000000..d6c8c723d6e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/uz_UZ@cyrillic.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mashrab Kuvatov Mashrab Kuvatov, Pablo Saratxaga kmashrab@uni-bremen.de, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/uz.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Январ', 'Феврал', 'Март', 'Апрел', 'Май', 'Июн', 'Июл', 'Август', 'Сентябр', 'Октябр', 'Ноябр', 'Декабр'], + 'months_short' => ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'], + 'weekdays' => ['Якшанба', 'Душанба', 'Сешанба', 'Чоршанба', 'Пайшанба', 'Жума', 'Шанба'], + 'weekdays_short' => ['Якш', 'Душ', 'Сеш', 'Чор', 'Пай', 'Жум', 'Шан'], + 'weekdays_min' => ['Якш', 'Душ', 'Сеш', 'Чор', 'Пай', 'Жум', 'Шан'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/vai.php b/libraries/Carbon/src/Carbon/Lang/vai.php new file mode 100644 index 00000000000..a53a398d19a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/vai.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'weekdays_short' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'weekdays_min' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'months' => ['ꖨꖕ ꕪꕴ ꔞꔀꕮꕊ', 'ꕒꕡꖝꖕ', 'ꕾꖺ', 'ꖢꖕ', 'ꖑꕱ', 'ꖱꘋ', 'ꖱꕞꔤ', 'ꗛꔕ', 'ꕢꕌ', 'ꕭꖃ', 'ꔞꘋꕔꕿ ꕸꖃꗏ', 'ꖨꖕ ꕪꕴ ꗏꖺꕮꕊ'], + 'months_short' => ['ꖨꖕꔞ', 'ꕒꕡ', 'ꕾꖺ', 'ꖢꖕ', 'ꖑꕱ', 'ꖱꘋ', 'ꖱꕞ', 'ꗛꔕ', 'ꕢꕌ', 'ꕭꖃ', 'ꔞꘋ', 'ꖨꖕꗏ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count ꕀ', // less reliable + 'y' => ':count ꕀ', // less reliable + 'a_year' => ':count ꕀ', // less reliable + + 'second' => ':count ꗱꕞꕯꕊ', // less reliable + 's' => ':count ꗱꕞꕯꕊ', // less reliable + 'a_second' => ':count ꗱꕞꕯꕊ', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/vai_Latn.php b/libraries/Carbon/src/Carbon/Lang/vai_Latn.php new file mode 100644 index 00000000000..c1206a3aada --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/vai_Latn.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'weekdays_short' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'weekdays_min' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'months' => ['luukao kemã', 'ɓandaɓu', 'vɔɔ', 'fulu', 'goo', '6', '7', 'kɔnde', 'saah', 'galo', 'kenpkato ɓololɔ', 'luukao lɔma'], + 'months_short' => ['luukao kemã', 'ɓandaɓu', 'vɔɔ', 'fulu', 'goo', '6', '7', 'kɔnde', 'saah', 'galo', 'kenpkato ɓololɔ', 'luukao lɔma'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/vai_Vaii.php b/libraries/Carbon/src/Carbon/Lang/vai_Vaii.php new file mode 100644 index 00000000000..3afe9641133 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/vai_Vaii.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/vai.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ve.php b/libraries/Carbon/src/Carbon/Lang/ve.php new file mode 100644 index 00000000000..8d3e5912c70 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ve.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ve_ZA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/ve_ZA.php b/libraries/Carbon/src/Carbon/Lang/ve_ZA.php new file mode 100644 index 00000000000..9a2725fdec9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/ve_ZA.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Phando', 'Luhuhi', 'Ṱhafamuhwe', 'Lambamai', 'Shundunthule', 'Fulwi', 'Fulwana', 'Ṱhangule', 'Khubvumedzi', 'Tshimedzi', 'Ḽara', 'Nyendavhusiku'], + 'months_short' => ['Pha', 'Luh', 'Fam', 'Lam', 'Shu', 'Lwi', 'Lwa', 'Ngu', 'Khu', 'Tsh', 'Ḽar', 'Nye'], + 'weekdays' => ['Swondaha', 'Musumbuluwo', 'Ḽavhuvhili', 'Ḽavhuraru', 'Ḽavhuṋa', 'Ḽavhuṱanu', 'Mugivhela'], + 'weekdays_short' => ['Swo', 'Mus', 'Vhi', 'Rar', 'ṋa', 'Ṱan', 'Mug'], + 'weekdays_min' => ['Swo', 'Mus', 'Vhi', 'Rar', 'ṋa', 'Ṱan', 'Mug'], + 'day_of_first_week_of_year' => 1, + + // Too unreliable + /* + 'day' => ':count vhege', // less reliable + 'd' => ':count vhege', // less reliable + 'a_day' => ':count vhege', // less reliable + + 'hour' => ':count watshi', // less reliable + 'h' => ':count watshi', // less reliable + 'a_hour' => ':count watshi', // less reliable + + 'minute' => ':count watshi', // less reliable + 'min' => ':count watshi', // less reliable + 'a_minute' => ':count watshi', // less reliable + + 'second' => ':count Mu', // less reliable + 's' => ':count Mu', // less reliable + 'a_second' => ':count Mu', // less reliable + + 'week' => ':count vhege', + 'w' => ':count vhege', + 'a_week' => ':count vhege', + */ +]); diff --git a/libraries/Carbon/src/Carbon/Lang/vi.php b/libraries/Carbon/src/Carbon/Lang/vi.php new file mode 100644 index 00000000000..77246b40f51 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/vi.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Andre Polykanine A.K.A. Menelion Elensúlë + * - JD Isaacks + */ +return [ + 'year' => ':count năm', + 'a_year' => '{1}một năm|]1, Inf[:count năm', + 'y' => ':count năm', + 'month' => ':count tháng', + 'a_month' => '{1}một tháng|]1, Inf[:count tháng', + 'm' => ':count tháng', + 'week' => ':count tuần', + 'a_week' => '{1}một tuần|]1, Inf[:count tuần', + 'w' => ':count tuần', + 'day' => ':count ngày', + 'a_day' => '{1}một ngày|]1, Inf[:count ngày', + 'd' => ':count ngày', + 'hour' => ':count giờ', + 'a_hour' => '{1}một giờ|]1, Inf[:count giờ', + 'h' => ':count giờ', + 'minute' => ':count phút', + 'a_minute' => '{1}một phút|]1, Inf[:count phút', + 'min' => ':count phút', + 'second' => ':count giây', + 'a_second' => '{1}vài giây|]1, Inf[:count giây', + 's' => ':count giây', + 'ago' => ':time trước', + 'from_now' => ':time tới', + 'after' => ':time sau', + 'before' => ':time trước', + 'diff_now' => 'bây giờ', + 'diff_today' => 'Hôm', + 'diff_today_regexp' => 'Hôm(?:\\s+nay)?(?:\\s+lúc)?', + 'diff_yesterday' => 'Hôm qua', + 'diff_yesterday_regexp' => 'Hôm(?:\\s+qua)?(?:\\s+lúc)?', + 'diff_tomorrow' => 'Ngày mai', + 'diff_tomorrow_regexp' => 'Ngày(?:\\s+mai)?(?:\\s+lúc)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [năm] YYYY', + 'LLL' => 'D MMMM [năm] YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM [năm] YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hôm nay lúc] LT', + 'nextDay' => '[Ngày mai lúc] LT', + 'nextWeek' => 'dddd [tuần tới lúc] LT', + 'lastDay' => '[Hôm qua lúc] LT', + 'lastWeek' => 'dddd [tuần trước lúc] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['SA', 'CH'], + 'months' => ['tháng 1', 'tháng 2', 'tháng 3', 'tháng 4', 'tháng 5', 'tháng 6', 'tháng 7', 'tháng 8', 'tháng 9', 'tháng 10', 'tháng 11', 'tháng 12'], + 'months_short' => ['Th01', 'Th02', 'Th03', 'Th04', 'Th05', 'Th06', 'Th07', 'Th08', 'Th09', 'Th10', 'Th11', 'Th12'], + 'weekdays' => ['chủ nhật', 'thứ hai', 'thứ ba', 'thứ tư', 'thứ năm', 'thứ sáu', 'thứ bảy'], + 'weekdays_short' => ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'], + 'weekdays_min' => ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' và '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/vi_VN.php b/libraries/Carbon/src/Carbon/Lang/vi_VN.php new file mode 100644 index 00000000000..b4e90f48b28 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/vi_VN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/vi.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/vo.php b/libraries/Carbon/src/Carbon/Lang/vo.php new file mode 100644 index 00000000000..111ffb834d1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/vo.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count yel', + 'y' => ':count yel', + 'a_year' => ':count yel', + + 'month' => ':count mul', + 'm' => ':count mul', + 'a_month' => ':count mul', + + 'week' => ':count vig', + 'w' => ':count vig', + 'a_week' => ':count vig', + + 'day' => ':count del', + 'd' => ':count del', + 'a_day' => ':count del', + + 'hour' => ':count düp', + 'h' => ':count düp', + 'a_hour' => ':count düp', + + 'minute' => ':count minut', + 'min' => ':count minut', + 'a_minute' => ':count minut', + + 'second' => ':count sekun', + 's' => ':count sekun', + 'a_second' => ':count sekun', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/vun.php b/libraries/Carbon/src/Carbon/Lang/vun.php new file mode 100644 index 00000000000..f11ba53eaa9 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/vun.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/wa.php b/libraries/Carbon/src/Carbon/Lang/wa.php new file mode 100644 index 00000000000..d982a0dad35 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/wa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wa_BE.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/wa_BE.php b/libraries/Carbon/src/Carbon/Lang/wa_BE.php new file mode 100644 index 00000000000..e6c103a1c31 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/wa_BE.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Djan SACRE Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['di djanvî', 'di fevrî', 'di måss', 'd’ avri', 'di may', 'di djun', 'di djulete', 'd’ awousse', 'di setimbe', 'd’ octôbe', 'di nôvimbe', 'di decimbe'], + 'months_short' => ['dja', 'fev', 'mås', 'avr', 'may', 'djn', 'djl', 'awo', 'set', 'oct', 'nôv', 'dec'], + 'weekdays' => ['dimegne', 'londi', 'mårdi', 'mierkidi', 'djudi', 'vénrdi', 'semdi'], + 'weekdays_short' => ['dim', 'lon', 'mår', 'mie', 'dju', 'vén', 'sem'], + 'weekdays_min' => ['dim', 'lon', 'mår', 'mie', 'dju', 'vén', 'sem'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count anêye', + 'y' => ':count anêye', + 'a_year' => ':count anêye', + + 'month' => ':count meûs', + 'm' => ':count meûs', + 'a_month' => ':count meûs', + + 'week' => ':count samwinne', + 'w' => ':count samwinne', + 'a_week' => ':count samwinne', + + 'day' => ':count djoû', + 'd' => ':count djoû', + 'a_day' => ':count djoû', + + 'hour' => ':count eure', + 'h' => ':count eure', + 'a_hour' => ':count eure', + + 'minute' => ':count munute', + 'min' => ':count munute', + 'a_minute' => ':count munute', + + 'second' => ':count Sigonde', + 's' => ':count Sigonde', + 'a_second' => ':count Sigonde', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/wae.php b/libraries/Carbon/src/Carbon/Lang/wae.php new file mode 100644 index 00000000000..d25cd99430f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/wae.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wae_CH.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/wae_CH.php b/libraries/Carbon/src/Carbon/Lang/wae_CH.php new file mode 100644 index 00000000000..ece5c04444b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/wae_CH.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Walser Translation Team ml@translate-wae.ch + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['Jenner', 'Hornig', 'Märze', 'Abrille', 'Meije', 'Bráčet', 'Heiwet', 'Öigšte', 'Herbštmánet', 'Wímánet', 'Wintermánet', 'Chrištmánet'], + 'months_short' => ['Jen', 'Hor', 'Mär', 'Abr', 'Mei', 'Brá', 'Hei', 'Öig', 'Her', 'Wím', 'Win', 'Chr'], + 'weekdays' => ['Suntag', 'Mäntag', 'Zischtag', 'Mittwuch', 'Frontag', 'Fritag', 'Samschtag'], + 'weekdays_short' => ['Sun', 'Män', 'Zis', 'Mit', 'Fro', 'Fri', 'Sam'], + 'weekdays_min' => ['Sun', 'Män', 'Zis', 'Mit', 'Fro', 'Fri', 'Sam'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'month' => ':count Maano', // less reliable + 'm' => ':count Maano', // less reliable + 'a_month' => ':count Maano', // less reliable +]); diff --git a/libraries/Carbon/src/Carbon/Lang/wal.php b/libraries/Carbon/src/Carbon/Lang/wal.php new file mode 100644 index 00000000000..e30f3ca3082 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/wal.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wal_ET.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/wal_ET.php b/libraries/Carbon/src/Carbon/Lang/wal_ET.php new file mode 100644 index 00000000000..f0146db3727 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/wal_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['ወጋ', 'ሳይኖ', 'ማቆሳኛ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ'], + 'weekdays_short' => ['ወጋ ', 'ሳይኖ', 'ማቆሳ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ '], + 'weekdays_min' => ['ወጋ ', 'ሳይኖ', 'ማቆሳ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ '], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ማለዶ', 'ቃማ'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/wo.php b/libraries/Carbon/src/Carbon/Lang/wo.php new file mode 100644 index 00000000000..d7725256256 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/wo.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wo_SN.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/wo_SN.php b/libraries/Carbon/src/Carbon/Lang/wo_SN.php new file mode 100644 index 00000000000..b15ce3ab94f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/wo_SN.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian Project Christian Perrier bubulle@debian.org + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'months' => ['sanwiy\'e', 'feebriy\'e', 'mars', 'awril', 'me', 'suwen', 'sulet', 'uut', 'septaambar', 'oktoobar', 'nowaambar', 'desaambar'], + 'months_short' => ['san', 'fee', 'mar', 'awr', 'me ', 'suw', 'sul', 'uut', 'sep', 'okt', 'now', 'des'], + 'weekdays' => ['dib\'eer', 'altine', 'talaata', 'allarba', 'alxames', 'ajjuma', 'gaawu'], + 'weekdays_short' => ['dib', 'alt', 'tal', 'all', 'alx', 'ajj', 'gaa'], + 'weekdays_min' => ['dib', 'alt', 'tal', 'all', 'alx', 'ajj', 'gaa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count at', + 'month' => ':count wèr', + 'week' => ':count ayubés', + 'day' => ':count bés', + 'hour' => ':count waxtu', + 'minute' => ':count simili', + 'second' => ':count saa', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/xh.php b/libraries/Carbon/src/Carbon/Lang/xh.php new file mode 100644 index 00000000000..dd9ce6bf42a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/xh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/xh_ZA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/xh_ZA.php b/libraries/Carbon/src/Carbon/Lang/xh_ZA.php new file mode 100644 index 00000000000..4755632d8e6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/xh_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['eyoMqungu', 'eyoMdumba', 'eyoKwindla', 'uTshazimpuzi', 'uCanzibe', 'eyeSilimela', 'eyeKhala', 'eyeThupa', 'eyoMsintsi', 'eyeDwarha', 'eyeNkanga', 'eyoMnga'], + 'months_short' => ['Mqu', 'Mdu', 'Kwi', 'Tsh', 'Can', 'Sil', 'Kha', 'Thu', 'Msi', 'Dwa', 'Nka', 'Mng'], + 'weekdays' => ['iCawa', 'uMvulo', 'lwesiBini', 'lwesiThathu', 'ulweSine', 'lwesiHlanu', 'uMgqibelo'], + 'weekdays_short' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'weekdays_min' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ihlobo', // less reliable + 'y' => ':count ihlobo', // less reliable + 'a_year' => ':count ihlobo', // less reliable + + 'hour' => ':count iwotshi', // less reliable + 'h' => ':count iwotshi', // less reliable + 'a_hour' => ':count iwotshi', // less reliable + + 'minute' => ':count ingqalelo', // less reliable + 'min' => ':count ingqalelo', // less reliable + 'a_minute' => ':count ingqalelo', // less reliable + + 'second' => ':count nceda', // less reliable + 's' => ':count nceda', // less reliable + 'a_second' => ':count nceda', // less reliable + + 'month' => ':count inyanga', + 'm' => ':count inyanga', + 'a_month' => ':count inyanga', + + 'week' => ':count veki', + 'w' => ':count veki', + 'a_week' => ':count veki', + + 'day' => ':count imini', + 'd' => ':count imini', + 'a_day' => ':count imini', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/xog.php b/libraries/Carbon/src/Carbon/Lang/xog.php new file mode 100644 index 00000000000..9e65ee9db5f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/xog.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Munkyo', 'Eigulo'], + 'weekdays' => ['Sabiiti', 'Balaza', 'Owokubili', 'Owokusatu', 'Olokuna', 'Olokutaanu', 'Olomukaaga'], + 'weekdays_short' => ['Sabi', 'Bala', 'Kubi', 'Kusa', 'Kuna', 'Kuta', 'Muka'], + 'weekdays_min' => ['Sabi', 'Bala', 'Kubi', 'Kusa', 'Kuna', 'Kuta', 'Muka'], + 'months' => ['Janwaliyo', 'Febwaliyo', 'Marisi', 'Apuli', 'Maayi', 'Juuni', 'Julaayi', 'Agusito', 'Sebuttemba', 'Okitobba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apu', 'Maa', 'Juu', 'Jul', 'Agu', 'Seb', 'Oki', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/yav.php b/libraries/Carbon/src/Carbon/Lang/yav.php new file mode 100644 index 00000000000..d800a464a48 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yav.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kiɛmɛ́ɛm', 'kisɛ́ndɛ'], + 'weekdays' => ['sɔ́ndiɛ', 'móndie', 'muányáŋmóndie', 'metúkpíápɛ', 'kúpélimetúkpiapɛ', 'feléte', 'séselé'], + 'weekdays_short' => ['sd', 'md', 'mw', 'et', 'kl', 'fl', 'ss'], + 'weekdays_min' => ['sd', 'md', 'mw', 'et', 'kl', 'fl', 'ss'], + 'months' => ['pikítíkítie, oólí ú kutúan', 'siɛyɛ́, oóli ú kándíɛ', 'ɔnsúmbɔl, oóli ú kátátúɛ', 'mesiŋ, oóli ú kénie', 'ensil, oóli ú kátánuɛ', 'ɔsɔn', 'efute', 'pisuyú', 'imɛŋ i puɔs', 'imɛŋ i putúk,oóli ú kátíɛ', 'makandikɛ', 'pilɔndɔ́'], + 'months_short' => ['o.1', 'o.2', 'o.3', 'o.4', 'o.5', 'o.6', 'o.7', 'o.8', 'o.9', 'o.10', 'o.11', 'o.12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/yi.php b/libraries/Carbon/src/Carbon/Lang/yi.php new file mode 100644 index 00000000000..2295f36a670 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yi_US.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/yi_US.php b/libraries/Carbon/src/Carbon/Lang/yi_US.php new file mode 100644 index 00000000000..f82ab14939e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yi_US.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - http://www.uyip.org/ Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['יאַנואַר', 'פֿעברואַר', 'מערץ', 'אַפּריל', 'מיי', 'יוני', 'יולי', 'אויגוסט', 'סעפּטעמבער', 'אקטאבער', 'נאוועמבער', 'דעצעמבער'], + 'months_short' => ['יאַנ', 'פֿעב', 'מאַר', 'אַפּר', 'מײַ ', 'יונ', 'יול', 'אױג', 'סעפּ', 'אָקט', 'נאָװ', 'דעצ'], + 'weekdays' => ['זונטיק', 'מאָנטיק', 'דינסטיק', 'מיטװאָך', 'דאָנערשטיק', 'פֿרײַטיק', 'שבת'], + 'weekdays_short' => ['זונ\'', 'מאָנ\'', 'דינ\'', 'מיט\'', 'דאָנ\'', 'פֿרײַ\'', 'שבת'], + 'weekdays_min' => ['זונ\'', 'מאָנ\'', 'דינ\'', 'מיט\'', 'דאָנ\'', 'פֿרײַ\'', 'שבת'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count יאר', + 'y' => ':count יאר', + 'a_year' => ':count יאר', + + 'month' => ':count חודש', + 'm' => ':count חודש', + 'a_month' => ':count חודש', + + 'week' => ':count וואָך', + 'w' => ':count וואָך', + 'a_week' => ':count וואָך', + + 'day' => ':count טאָג', + 'd' => ':count טאָג', + 'a_day' => ':count טאָג', + + 'hour' => ':count שעה', + 'h' => ':count שעה', + 'a_hour' => ':count שעה', + + 'minute' => ':count מינוט', + 'min' => ':count מינוט', + 'a_minute' => ':count מינוט', + + 'second' => ':count סעקונדע', + 's' => ':count סעקונדע', + 'a_second' => ':count סעקונדע', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/yo.php b/libraries/Carbon/src/Carbon/Lang/yo.php new file mode 100644 index 00000000000..83e674d69bf --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yo.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Atolagbe Abisoye + */ +return [ + 'year' => 'ọdún :count', + 'a_year' => '{1}ọdún kan|ọdún :count', + 'month' => 'osù :count', + 'a_month' => '{1}osù kan|osù :count', + 'week' => 'ọsẹ :count', + 'a_week' => '{1}ọsẹ kan|ọsẹ :count', + 'day' => 'ọjọ́ :count', + 'a_day' => '{1}ọjọ́ kan|ọjọ́ :count', + 'hour' => 'wákati :count', + 'a_hour' => '{1}wákati kan|wákati :count', + 'minute' => 'ìsẹjú :count', + 'a_minute' => '{1}ìsẹjú kan|ìsẹjú :count', + 'second' => 'iaayá :count', + 'a_second' => '{1}ìsẹjú aayá die|aayá :count', + 'ago' => ':time kọjá', + 'from_now' => 'ní :time', + 'diff_yesterday' => 'Àna', + 'diff_yesterday_regexp' => 'Àna(?:\\s+ni)?', + 'diff_today' => 'Ònì', + 'diff_today_regexp' => 'Ònì(?:\\s+ni)?', + 'diff_tomorrow' => 'Ọ̀la', + 'diff_tomorrow_regexp' => 'Ọ̀la(?:\\s+ni)?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Ònì ni] LT', + 'nextDay' => '[Ọ̀la ni] LT', + 'nextWeek' => 'dddd [Ọsẹ̀ tón\'bọ] [ni] LT', + 'lastDay' => '[Àna ni] LT', + 'lastWeek' => 'dddd [Ọsẹ̀ tólọ́] [ni] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ọjọ́ :number', + 'months' => ['Sẹ́rẹ́', 'Èrèlè', 'Ẹrẹ̀nà', 'Ìgbé', 'Èbibi', 'Òkùdu', 'Agẹmo', 'Ògún', 'Owewe', 'Ọ̀wàrà', 'Bélú', 'Ọ̀pẹ̀̀'], + 'months_short' => ['Sẹ́r', 'Èrl', 'Ẹrn', 'Ìgb', 'Èbi', 'Òkù', 'Agẹ', 'Ògú', 'Owe', 'Ọ̀wà', 'Bél', 'Ọ̀pẹ̀̀'], + 'weekdays' => ['Àìkú', 'Ajé', 'Ìsẹ́gun', 'Ọjọ́rú', 'Ọjọ́bọ', 'Ẹtì', 'Àbámẹ́ta'], + 'weekdays_short' => ['Àìk', 'Ajé', 'Ìsẹ́', 'Ọjr', 'Ọjb', 'Ẹtì', 'Àbá'], + 'weekdays_min' => ['Àì', 'Aj', 'Ìs', 'Ọr', 'Ọb', 'Ẹt', 'Àb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'meridiem' => ['Àárọ̀', 'Ọ̀sán'], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/yo_BJ.php b/libraries/Carbon/src/Carbon/Lang/yo_BJ.php new file mode 100644 index 00000000000..2d689bc30db --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yo_BJ.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/yo.php', [ + 'meridiem' => ['Àárɔ̀', 'Ɔ̀sán'], + 'weekdays' => ['Ɔjɔ́ Àìkú', 'Ɔjɔ́ Ajé', 'Ɔjɔ́ Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɔjɔ́ Ɛtì', 'Ɔjɔ́ Àbámɛ́ta'], + 'weekdays_short' => ['Àìkú', 'Ajé', 'Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɛtì', 'Àbámɛ́ta'], + 'weekdays_min' => ['Àìkú', 'Ajé', 'Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɛtì', 'Àbámɛ́ta'], + 'months' => ['Oshù Shɛ́rɛ́', 'Oshù Èrèlè', 'Oshù Ɛrɛ̀nà', 'Oshù Ìgbé', 'Oshù Ɛ̀bibi', 'Oshù Òkúdu', 'Oshù Agɛmɔ', 'Oshù Ògún', 'Oshù Owewe', 'Oshù Ɔ̀wàrà', 'Oshù Bélú', 'Oshù Ɔ̀pɛ̀'], + 'months_short' => ['Shɛ́rɛ́', 'Èrèlè', 'Ɛrɛ̀nà', 'Ìgbé', 'Ɛ̀bibi', 'Òkúdu', 'Agɛmɔ', 'Ògún', 'Owewe', 'Ɔ̀wàrà', 'Bélú', 'Ɔ̀pɛ̀'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/yo_NG.php b/libraries/Carbon/src/Carbon/Lang/yo_NG.php new file mode 100644 index 00000000000..bbe1a93df3c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yo_NG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/yo.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/yue.php b/libraries/Carbon/src/Carbon/Lang/yue.php new file mode 100644 index 00000000000..a5df81c14a8 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yue.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yue_HK.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/yue_HK.php b/libraries/Carbon/src/Carbon/Lang/yue_HK.php new file mode 100644 index 00000000000..6e47e4c54ec --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yue_HK.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh_HK.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日 dddd', + ], + 'months' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['上午', '下午'], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/yue_Hans.php b/libraries/Carbon/src/Carbon/Lang/yue_Hans.php new file mode 100644 index 00000000000..c15bd8fea5f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yue_Hans.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/yue_Hant.php b/libraries/Carbon/src/Carbon/Lang/yue_Hant.php new file mode 100644 index 00000000000..c9fb9df4da6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yue_Hant.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/yuw.php b/libraries/Carbon/src/Carbon/Lang/yuw.php new file mode 100644 index 00000000000..f854de90977 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yuw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yuw_PG.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/yuw_PG.php b/libraries/Carbon/src/Carbon/Lang/yuw_PG.php new file mode 100644 index 00000000000..69a600a91aa --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/yuw_PG.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from native speakers Hannah Sarvasy nungon.localization@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['jenuari', 'febuari', 'mas', 'epril', 'mei', 'jun', 'julai', 'ögus', 'septemba', 'öktoba', 'nöwemba', 'diksemba'], + 'months_short' => ['jen', 'feb', 'mas', 'epr', 'mei', 'jun', 'jul', 'ögu', 'sep', 'ökt', 'nöw', 'dis'], + 'weekdays' => ['sönda', 'mönda', 'sinda', 'mitiwö', 'sogipbono', 'nenggo', 'söndanggie'], + 'weekdays_short' => ['sön', 'mön', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'weekdays_min' => ['sön', 'mön', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/zgh.php b/libraries/Carbon/src/Carbon/Lang/zgh.php new file mode 100644 index 00000000000..25543cc0c9f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zgh.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - BAKTETE Miloud + */ +return [ + 'year' => ':count ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'a_year' => 'ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'y' => ':count ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'month' => ':count ⵡⴰⵢⵢⵓⵔ|:count ⴰⵢⵢⵓⵔⵏ', + 'a_month' => 'ⵉⴷⵊ ⵡⴰⵢⵢⵓⵔ|:count ⴰⵢⵢⵓⵔⵏ', + 'm' => ':count ⴰⵢⵢⵓⵔⵏ', + 'week' => ':count ⵉⵎⴰⵍⴰⵙⵙ|:count ⵉⵎⴰⵍⴰⵙⵙⵏ', + 'a_week' => 'ⵉⵛⵜ ⵉⵎⴰⵍⴰⵙⵙ|:count ⵉⵎⴰⵍⴰⵙⵙⵏ', + 'w' => ':count ⵉⵎⴰⵍⴰⵙⵙ.', + 'day' => ':count ⵡⴰⵙⵙ|:count ⵓⵙⵙⴰⵏ', + 'a_day' => 'ⵉⴷⵊ ⵡⴰⵙⵙ|:count ⵓⵙⵙⴰⵏ', + 'd' => ':count ⵓ', + 'hour' => ':count ⵜⵙⵔⴰⴳⵜ|:count ⵜⵉⵙⵔⴰⴳⵉⵏ', + 'a_hour' => 'ⵉⵛⵜ ⵜⵙⵔⴰⴳⵜ|:count ⵜⵉⵙⵔⴰⴳⵉⵏ', + 'h' => ':count ⵜ', + 'minute' => ':count ⵜⵓⵙⴷⵉⴷⵜ|:count ⵜⵓⵙⴷⵉⴷⵉⵏ', + 'a_minute' => 'ⵉⵛⵜ ⵜⵓⵙⴷⵉⴷⵜ|:count ⵜⵓⵙⴷⵉⴷⵉⵏ', + 'min' => ':count ⵜⵓⵙ', + 'second' => ':count ⵜⵙⵉⵏⵜ|:count ⵜⵉⵙⵉⵏⴰ', + 'a_second' => 'ⴽⵔⴰ ⵜⵉⵙⵉⵏⴰ|:count ⵜⵉⵙⵉⵏⴰ', + 's' => ':count ⵜ', + 'ago' => 'ⵣⴳ :time', + 'from_now' => 'ⴷⴳ :time', + 'after' => ':time ⴰⵡⴰⵔ', + 'before' => ':time ⴷⴰⵜ', + 'diff_now' => 'ⴰⴷⵡⴰⵍⵉ', + 'diff_today' => 'ⴰⵙⵙ', + 'diff_today_regexp' => 'ⴰⵙⵙ(?:\\s+ⴰ/ⴰⴷ)?(?:\\s+ⴳ)?', + 'diff_yesterday' => 'ⴰⵙⵙⵏⵏⴰⵟ', + 'diff_yesterday_regexp' => 'ⴰⵙⵙⵏⵏⴰⵟ(?:\\s+ⴳ)?', + 'diff_tomorrow' => 'ⴰⵙⴽⴽⴰ', + 'diff_tomorrow_regexp' => 'ⴰⵙⴽⴽⴰ(?:\\s+ⴳ)?', + 'diff_before_yesterday' => 'ⴼⵔ ⵉⴹⵏⵏⴰⵟ', + 'diff_after_tomorrow' => 'ⵏⴰⴼ ⵓⵙⴽⴽⴰ', + 'period_recurrences' => ':count ⵜⵉⴽⴽⴰⵍ', + 'period_interval' => 'ⴽⵓ :interval', + 'period_start_date' => 'ⴳ :date', + 'period_end_date' => 'ⵉ :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ⴰⵙⵙ ⴰ/ⴰⴷ ⴳ] LT', + 'nextDay' => '[ⴰⵙⴽⴽⴰ ⴳ] LT', + 'nextWeek' => 'dddd [ⴳ] LT', + 'lastDay' => '[ⴰⵙⵙⵏⵏⴰⵟ ⴳ] LT', + 'lastWeek' => 'dddd [ⴰⵎⴳⴳⴰⵔⵓ ⴳ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ⵜⵉⴼⴰⵡⵜ', 'ⵜⴰⴷⴳⴳⵯⴰⵜ'], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⴰⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏ', 'ⴱⵕⴰ', 'ⵎⴰⵕ', 'ⵉⴱⵔ', 'ⵎⴰⵢ', 'ⵢⵓⵏ', 'ⵢⵓⵍ', 'ⵖⵓⵛ', 'ⵛⵓⵜ', 'ⴽⵟⵓ', 'ⵏⵓⵡ', 'ⴷⵓⵊ'], + 'weekdays' => ['ⵓⵙⴰⵎⴰⵙ', 'ⵡⴰⵢⵏⴰⵙ', 'ⵓⵙⵉⵏⴰⵙ', 'ⵡⴰⴽⵕⴰⵙ', 'ⵓⴽⵡⴰⵙ', 'ⵓⵙⵉⵎⵡⴰⵙ', 'ⵓⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⵓⵙⴰ', 'ⵡⴰⵢ', 'ⵓⵙⵉ', 'ⵡⴰⴽ', 'ⵓⴽⵡ', 'ⵓⵙⵉⵎ', 'ⵓⵙⵉⴹ'], + 'weekdays_min' => ['ⵓⵙⴰ', 'ⵡⴰⵢ', 'ⵓⵙⵉ', 'ⵡⴰⴽ', 'ⵓⴽⵡ', 'ⵓⵙⵉⵎ', 'ⵓⵙⵉⴹ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ⴷ '], +]; diff --git a/libraries/Carbon/src/Carbon/Lang/zh.php b/libraries/Carbon/src/Carbon/Lang/zh.php new file mode 100644 index 00000000000..e2392448cb2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - xuri + * - sycuato + * - bokideckonja + * - Luo Ning + * - William Yang (williamyang233) + */ +return array_merge(require __DIR__.'/zh_Hans.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 A h点mm分', + 'LLLL' => 'YYYY年M月D日dddd A h点mm分', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/zh_CN.php b/libraries/Carbon/src/Carbon/Lang/zh_CN.php new file mode 100644 index 00000000000..21589d4bdec --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_CN.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Serhan Apaydın + * - Matt Johnson + * - JD Isaacks + * - Zeno Zeng + * - Chris Hemp + * - shankesgk2 + */ +return array_merge(require __DIR__.'/zh.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日Ah点mm分', + 'LLLL' => 'YYYY年M月D日ddddAh点mm分', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/zh_HK.php b/libraries/Carbon/src/Carbon/Lang/zh_HK.php new file mode 100644 index 00000000000..efb6d63f4ab --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant_HK.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_Hans.php b/libraries/Carbon/src/Carbon/Lang/zh_Hans.php new file mode 100644 index 00000000000..22cb8926fcc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_Hans.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Konstantin Konev + * - Chris Lam + * - Serhan Apaydın + * - Gary Lo + * - JD Isaacks + * - Chris Hemp + * - shankesgk2 + * - Daniel Cheung (danvim) + */ +return [ + 'year' => ':count:optional-space年', + 'y' => ':count:optional-space年', + 'month' => ':count:optional-space个月', + 'm' => ':count:optional-space个月', + 'week' => ':count:optional-space周', + 'w' => ':count:optional-space周', + 'day' => ':count:optional-space天', + 'd' => ':count:optional-space天', + 'hour' => ':count:optional-space小时', + 'h' => ':count:optional-space小时', + 'minute' => ':count:optional-space分钟', + 'min' => ':count:optional-space分钟', + 'second' => ':count:optional-space秒', + 'a_second' => '{1}几秒|]1,Inf[:count:optional-space秒', + 's' => ':count:optional-space秒', + 'ago' => ':time前', + 'from_now' => ':time后', + 'after' => ':time后', + 'before' => ':time前', + 'diff_now' => '现在', + 'diff_today' => '今天', + 'diff_yesterday' => '昨天', + 'diff_tomorrow' => '明天', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今天]LT', + 'nextDay' => '[明天]LT', + 'nextWeek' => '[下]ddddLT', + 'lastDay' => '[昨天]LT', + 'lastWeek' => '[上]ddddLT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + case 'M': + return $number.'月'; + case 'w': + case 'W': + return $number.'周'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return '凌晨'; + } + if ($time < 900) { + return '早上'; + } + if ($time < 1130) { + return '上午'; + } + if ($time < 1230) { + return '中午'; + } + if ($time < 1800) { + return '下午'; + } + + return '晚上'; + }, + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => '', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_Hans_HK.php b/libraries/Carbon/src/Carbon/Lang/zh_Hans_HK.php new file mode 100644 index 00000000000..c15bd8fea5f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_Hans_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_Hans_MO.php b/libraries/Carbon/src/Carbon/Lang/zh_Hans_MO.php new file mode 100644 index 00000000000..c15bd8fea5f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_Hans_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_Hans_SG.php b/libraries/Carbon/src/Carbon/Lang/zh_Hans_SG.php new file mode 100644 index 00000000000..c15bd8fea5f --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_Hans_SG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_Hant.php b/libraries/Carbon/src/Carbon/Lang/zh_Hant.php new file mode 100644 index 00000000000..7ebe20bc0b2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_Hant.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Adam + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Chris Lam + * - Serhan Apaydın + * - Gary Lo + * - JD Isaacks + * - Chris Hemp + * - Eddie + * - KID + * - shankesgk2 + * - Daniel Cheung (danvim) + */ +return [ + 'year' => ':count:optional-space年', + 'y' => ':count:optional-space年', + 'month' => ':count:optional-space個月', + 'm' => ':count:optional-space月', + 'week' => ':count:optional-space週', + 'w' => ':count:optional-space週', + 'day' => ':count:optional-space天', + 'd' => ':count:optional-space天', + 'hour' => ':count:optional-space小時', + 'h' => ':count:optional-space小時', + 'minute' => ':count:optional-space分鐘', + 'min' => ':count:optional-space分鐘', + 'second' => ':count:optional-space秒', + 'a_second' => '{1}幾秒|]1,Inf[:count:optional-space秒', + 's' => ':count:optional-space秒', + 'ago' => ':time前', + 'from_now' => ':time後', + 'after' => ':time後', + 'before' => ':time前', + 'diff_now' => '現在', + 'diff_today' => '今天', + 'diff_yesterday' => '昨天', + 'diff_tomorrow' => '明天', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今天] LT', + 'nextDay' => '[明天] LT', + 'nextWeek' => '[下]dddd LT', + 'lastDay' => '[昨天] LT', + 'lastWeek' => '[上]dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + case 'M': + return $number.'月'; + case 'w': + case 'W': + return $number.'周'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return '凌晨'; + } + if ($time < 900) { + return '早上'; + } + if ($time < 1130) { + return '上午'; + } + if ($time < 1230) { + return '中午'; + } + if ($time < 1800) { + return '下午'; + } + + return '晚上'; + }, + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['週日', '週一', '週二', '週三', '週四', '週五', '週六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => '', +]; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_Hant_HK.php b/libraries/Carbon/src/Carbon/Lang/zh_Hant_HK.php new file mode 100644 index 00000000000..c9fb9df4da6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_Hant_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_Hant_MO.php b/libraries/Carbon/src/Carbon/Lang/zh_Hant_MO.php new file mode 100644 index 00000000000..c9fb9df4da6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_Hant_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_Hant_TW.php b/libraries/Carbon/src/Carbon/Lang/zh_Hant_TW.php new file mode 100644 index 00000000000..c9fb9df4da6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_Hant_TW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_MO.php b/libraries/Carbon/src/Carbon/Lang/zh_MO.php new file mode 100644 index 00000000000..61ad0b9a05c --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_MO.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - tarunvelli + * - Eddie + * - KID + * - shankesgk2 + */ +return array_replace_recursive(require __DIR__.'/zh_Hant.php', [ + 'after' => ':time后', +]); diff --git a/libraries/Carbon/src/Carbon/Lang/zh_SG.php b/libraries/Carbon/src/Carbon/Lang/zh_SG.php new file mode 100644 index 00000000000..a69c23a3b56 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_SG.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/libraries/Carbon/src/Carbon/Lang/zh_TW.php b/libraries/Carbon/src/Carbon/Lang/zh_TW.php new file mode 100644 index 00000000000..b7cf6478a53 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_TW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant_TW.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zh_YUE.php b/libraries/Carbon/src/Carbon/Lang/zh_YUE.php new file mode 100644 index 00000000000..18d58cc003e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zh_YUE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/libraries/Carbon/src/Carbon/Lang/zu.php b/libraries/Carbon/src/Carbon/Lang/zu.php new file mode 100644 index 00000000000..164629c7b2a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/zu_ZA.php'; diff --git a/libraries/Carbon/src/Carbon/Lang/zu_ZA.php b/libraries/Carbon/src/Carbon/Lang/zu_ZA.php new file mode 100644 index 00000000000..ed4d44729c0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Lang/zu_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januwari', 'Februwari', 'Mashi', 'Ephreli', 'Meyi', 'Juni', 'Julayi', 'Agasti', 'Septhemba', 'Okthoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mas', 'Eph', 'Mey', 'Jun', 'Jul', 'Aga', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['iSonto', 'uMsombuluko', 'uLwesibili', 'uLwesithathu', 'uLwesine', 'uLwesihlanu', 'uMgqibelo'], + 'weekdays_short' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'weekdays_min' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'kweminyaka engu-:count', + 'y' => 'kweminyaka engu-:count', + 'a_year' => 'kweminyaka engu-:count', + + 'month' => 'izinyanga ezingu-:count', + 'm' => 'izinyanga ezingu-:count', + 'a_month' => 'izinyanga ezingu-:count', + + 'week' => 'lwamasonto angu-:count', + 'w' => 'lwamasonto angu-:count', + 'a_week' => 'lwamasonto angu-:count', + + 'day' => 'ezingaba ngu-:count', + 'd' => 'ezingaba ngu-:count', + 'a_day' => 'ezingaba ngu-:count', + + 'hour' => 'amahora angu-:count', + 'h' => 'amahora angu-:count', + 'a_hour' => 'amahora angu-:count', + + 'minute' => 'ngemizuzu engu-:count', + 'min' => 'ngemizuzu engu-:count', + 'a_minute' => 'ngemizuzu engu-:count', + + 'second' => 'imizuzwana engu-:count', + 's' => 'imizuzwana engu-:count', + 'a_second' => 'imizuzwana engu-:count', +]); diff --git a/libraries/Carbon/src/Carbon/Language.php b/libraries/Carbon/src/Carbon/Language.php new file mode 100644 index 00000000000..e959f73a657 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Language.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use JsonSerializable; +use ReturnTypeWillChange; + +class Language implements JsonSerializable +{ + /** + * @var array + */ + protected static $languagesNames; + + /** + * @var array + */ + protected static $regionsNames; + + /** + * @var string + */ + protected $id; + + /** + * @var string + */ + protected $code; + + /** + * @var string|null + */ + protected $variant; + + /** + * @var string|null + */ + protected $region; + + /** + * @var array + */ + protected $names; + + /** + * @var string + */ + protected $isoName; + + /** + * @var string + */ + protected $nativeName; + + public function __construct(string $id) + { + $this->id = str_replace('-', '_', $id); + $parts = explode('_', $this->id); + $this->code = $parts[0]; + + if (isset($parts[1])) { + if (!preg_match('/^[A-Z]+$/', $parts[1])) { + $this->variant = $parts[1]; + $parts[1] = $parts[2] ?? null; + } + if ($parts[1]) { + $this->region = $parts[1]; + } + } + } + + /** + * Get the list of the known languages. + * + * @return array + */ + public static function all() + { + if (!static::$languagesNames) { + static::$languagesNames = require __DIR__.'/List/languages.php'; + } + + return static::$languagesNames; + } + + /** + * Get the list of the known regions. + * + * @return array + */ + public static function regions() + { + if (!static::$regionsNames) { + static::$regionsNames = require __DIR__.'/List/regions.php'; + } + + return static::$regionsNames; + } + + /** + * Get both isoName and nativeName as an array. + * + * @return array + */ + public function getNames(): array + { + if (!$this->names) { + $this->names = static::all()[$this->code] ?? [ + 'isoName' => $this->code, + 'nativeName' => $this->code, + ]; + } + + return $this->names; + } + + /** + * Returns the original locale ID. + * + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * Returns the code of the locale "en"/"fr". + * + * @return string + */ + public function getCode(): string + { + return $this->code; + } + + /** + * Returns the variant code such as cyrl/latn. + * + * @return string|null + */ + public function getVariant(): ?string + { + return $this->variant; + } + + /** + * Returns the variant such as Cyrillic/Latin. + * + * @return string|null + */ + public function getVariantName(): ?string + { + if ($this->variant === 'Latn') { + return 'Latin'; + } + + if ($this->variant === 'Cyrl') { + return 'Cyrillic'; + } + + return $this->variant; + } + + /** + * Returns the region part of the locale. + * + * @return string|null + */ + public function getRegion(): ?string + { + return $this->region; + } + + /** + * Returns the region name for the current language. + * + * @return string|null + */ + public function getRegionName(): ?string + { + return $this->region ? (static::regions()[$this->region] ?? $this->region) : null; + } + + /** + * Returns the long ISO language name. + * + * @return string + */ + public function getFullIsoName(): string + { + if (!$this->isoName) { + $this->isoName = $this->getNames()['isoName']; + } + + return $this->isoName; + } + + /** + * Set the ISO language name. + * + * @param string $isoName + */ + public function setIsoName(string $isoName): self + { + $this->isoName = $isoName; + + return $this; + } + + /** + * Return the full name of the language in this language. + * + * @return string + */ + public function getFullNativeName(): string + { + if (!$this->nativeName) { + $this->nativeName = $this->getNames()['nativeName']; + } + + return $this->nativeName; + } + + /** + * Set the name of the language in this language. + * + * @param string $nativeName + */ + public function setNativeName(string $nativeName): self + { + $this->nativeName = $nativeName; + + return $this; + } + + /** + * Returns the short ISO language name. + * + * @return string + */ + public function getIsoName(): string + { + $name = $this->getFullIsoName(); + + return trim(strstr($name, ',', true) ?: $name); + } + + /** + * Get the short name of the language in this language. + * + * @return string + */ + public function getNativeName(): string + { + $name = $this->getFullNativeName(); + + return trim(strstr($name, ',', true) ?: $name); + } + + /** + * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getIsoDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getIsoName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with short native name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getNativeDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getNativeName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with long ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getFullIsoDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getFullIsoName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with long native name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getFullNativeDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getFullNativeName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Returns the original locale ID. + * + * @return string + */ + public function __toString() + { + return $this->getId(); + } + + /** + * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getIsoDescription(); + } +} diff --git a/libraries/Carbon/src/Carbon/Laravel/ServiceProvider.php b/libraries/Carbon/src/Carbon/Laravel/ServiceProvider.php new file mode 100644 index 00000000000..0287ac8254a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Laravel/ServiceProvider.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Laravel; + +use EDD\Vendor\Carbon\Carbon; +use EDD\Vendor\Carbon\CarbonImmutable; +use EDD\Vendor\Carbon\CarbonInterval; +use EDD\Vendor\Carbon\CarbonPeriod; +use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; +use Illuminate\Events\Dispatcher; +use Illuminate\Events\EventDispatcher; +use Illuminate\Support\Carbon as IlluminateCarbon; +use Illuminate\Support\Facades\Date; +use Throwable; + +class ServiceProvider extends \Illuminate\Support\ServiceProvider +{ + /** @var callable|null */ + protected $appGetter = null; + + /** @var callable|null */ + protected $localeGetter = null; + + public function setAppGetter(?callable $appGetter): void + { + $this->appGetter = $appGetter; + } + + public function setLocaleGetter(?callable $localeGetter): void + { + $this->localeGetter = $localeGetter; + } + + public function boot() + { + $this->updateLocale(); + + if (!$this->app->bound('events')) { + return; + } + + $service = $this; + $events = $this->app['events']; + + if ($this->isEventDispatcher($events)) { + $events->listen(class_exists('Illuminate\Foundation\Events\LocaleUpdated') ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed', function () use ($service) { + $service->updateLocale(); + }); + } + } + + public function updateLocale() + { + $locale = $this->getLocale(); + + if ($locale === null) { + return; + } + + Carbon::setLocale($locale); + CarbonImmutable::setLocale($locale); + CarbonPeriod::setLocale($locale); + CarbonInterval::setLocale($locale); + + if (class_exists(IlluminateCarbon::class)) { + IlluminateCarbon::setLocale($locale); + } + + if (class_exists(Date::class)) { + try { + $root = Date::getFacadeRoot(); + $root->setLocale($locale); + } catch (Throwable $e) { + // Non EDD\Vendor\Carbon class in use in Date facade + } + } + } + + public function register() + { + // Needed for Laravel < 5.3 compatibility + } + + protected function getLocale() + { + if ($this->localeGetter) { + return ($this->localeGetter)(); + } + + $app = $this->getApp(); + $app = $app && method_exists($app, 'getLocale') + ? $app + : $this->getGlobalApp('translator'); + + return $app ? $app->getLocale() : null; + } + + protected function getApp() + { + if ($this->appGetter) { + return ($this->appGetter)(); + } + + return $this->app ?? $this->getGlobalApp(); + } + + protected function getGlobalApp(...$args) + { + return \function_exists('app') ? \app(...$args) : null; + } + + protected function isEventDispatcher($instance) + { + return $instance instanceof EventDispatcher + || $instance instanceof Dispatcher + || $instance instanceof DispatcherContract; + } +} diff --git a/libraries/Carbon/src/Carbon/List/languages.php b/libraries/Carbon/src/Carbon/List/languages.php new file mode 100644 index 00000000000..7a0ab057707 --- /dev/null +++ b/libraries/Carbon/src/Carbon/List/languages.php @@ -0,0 +1,1239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + /* + * ISO 639-2 + */ + 'ab' => [ + 'isoName' => 'Abkhazian', + 'nativeName' => 'аҧсуа бызшәа, аҧсшәа', + ], + 'aa' => [ + 'isoName' => 'Afar', + 'nativeName' => 'Afaraf', + ], + 'af' => [ + 'isoName' => 'Afrikaans', + 'nativeName' => 'Afrikaans', + ], + 'ak' => [ + 'isoName' => 'Akan', + 'nativeName' => 'Akan', + ], + 'sq' => [ + 'isoName' => 'Albanian', + 'nativeName' => 'Shqip', + ], + 'am' => [ + 'isoName' => 'Amharic', + 'nativeName' => 'አማርኛ', + ], + 'ar' => [ + 'isoName' => 'Arabic', + 'nativeName' => 'العربية', + ], + 'an' => [ + 'isoName' => 'Aragonese', + 'nativeName' => 'aragonés', + ], + 'hy' => [ + 'isoName' => 'Armenian', + 'nativeName' => 'Հայերեն', + ], + 'as' => [ + 'isoName' => 'Assamese', + 'nativeName' => 'অসমীয়া', + ], + 'av' => [ + 'isoName' => 'Avaric', + 'nativeName' => 'авар мацӀ, магӀарул мацӀ', + ], + 'ae' => [ + 'isoName' => 'Avestan', + 'nativeName' => 'avesta', + ], + 'ay' => [ + 'isoName' => 'Aymara', + 'nativeName' => 'aymar aru', + ], + 'az' => [ + 'isoName' => 'Azerbaijani', + 'nativeName' => 'azərbaycan dili', + ], + 'bm' => [ + 'isoName' => 'Bambara', + 'nativeName' => 'bamanankan', + ], + 'ba' => [ + 'isoName' => 'Bashkir', + 'nativeName' => 'башҡорт теле', + ], + 'eu' => [ + 'isoName' => 'Basque', + 'nativeName' => 'euskara, euskera', + ], + 'be' => [ + 'isoName' => 'Belarusian', + 'nativeName' => 'беларуская мова', + ], + 'bn' => [ + 'isoName' => 'Bengali', + 'nativeName' => 'বাংলা', + ], + 'bh' => [ + 'isoName' => 'Bihari languages', + 'nativeName' => 'भोजपुरी', + ], + 'bi' => [ + 'isoName' => 'Bislama', + 'nativeName' => 'Bislama', + ], + 'bs' => [ + 'isoName' => 'Bosnian', + 'nativeName' => 'bosanski jezik', + ], + 'br' => [ + 'isoName' => 'Breton', + 'nativeName' => 'brezhoneg', + ], + 'bg' => [ + 'isoName' => 'Bulgarian', + 'nativeName' => 'български език', + ], + 'my' => [ + 'isoName' => 'Burmese', + 'nativeName' => 'ဗမာစာ', + ], + 'ca' => [ + 'isoName' => 'Catalan, Valencian', + 'nativeName' => 'català, valencià', + ], + 'ch' => [ + 'isoName' => 'Chamorro', + 'nativeName' => 'Chamoru', + ], + 'ce' => [ + 'isoName' => 'Chechen', + 'nativeName' => 'нохчийн мотт', + ], + 'ny' => [ + 'isoName' => 'Chichewa, Chewa, Nyanja', + 'nativeName' => 'chiCheŵa, chinyanja', + ], + 'zh' => [ + 'isoName' => 'Chinese', + 'nativeName' => '中文 (Zhōngwén), 汉语, 漢語', + ], + 'cv' => [ + 'isoName' => 'Chuvash', + 'nativeName' => 'чӑваш чӗлхи', + ], + 'kw' => [ + 'isoName' => 'Cornish', + 'nativeName' => 'Kernewek', + ], + 'co' => [ + 'isoName' => 'Corsican', + 'nativeName' => 'corsu, lingua corsa', + ], + 'cr' => [ + 'isoName' => 'Cree', + 'nativeName' => 'ᓀᐦᐃᔭᐍᐏᐣ', + ], + 'hr' => [ + 'isoName' => 'Croatian', + 'nativeName' => 'hrvatski jezik', + ], + 'cs' => [ + 'isoName' => 'Czech', + 'nativeName' => 'čeština, český jazyk', + ], + 'da' => [ + 'isoName' => 'Danish', + 'nativeName' => 'dansk', + ], + 'dv' => [ + 'isoName' => 'Divehi, Dhivehi, Maldivian', + 'nativeName' => 'ދިވެހި', + ], + 'nl' => [ + 'isoName' => 'Dutch, Flemish', + 'nativeName' => 'Nederlands, Vlaams', + ], + 'dz' => [ + 'isoName' => 'Dzongkha', + 'nativeName' => 'རྫོང་ཁ', + ], + 'en' => [ + 'isoName' => 'English', + 'nativeName' => 'English', + ], + 'eo' => [ + 'isoName' => 'Esperanto', + 'nativeName' => 'Esperanto', + ], + 'et' => [ + 'isoName' => 'Estonian', + 'nativeName' => 'eesti, eesti keel', + ], + 'ee' => [ + 'isoName' => 'Ewe', + 'nativeName' => 'Eʋegbe', + ], + 'fo' => [ + 'isoName' => 'Faroese', + 'nativeName' => 'føroyskt', + ], + 'fj' => [ + 'isoName' => 'Fijian', + 'nativeName' => 'vosa Vakaviti', + ], + 'fi' => [ + 'isoName' => 'Finnish', + 'nativeName' => 'suomi, suomen kieli', + ], + 'fr' => [ + 'isoName' => 'French', + 'nativeName' => 'français', + ], + 'ff' => [ + 'isoName' => 'Fulah', + 'nativeName' => 'Fulfulde, Pulaar, Pular', + ], + 'gl' => [ + 'isoName' => 'Galician', + 'nativeName' => 'Galego', + ], + 'ka' => [ + 'isoName' => 'Georgian', + 'nativeName' => 'ქართული', + ], + 'de' => [ + 'isoName' => 'German', + 'nativeName' => 'Deutsch', + ], + 'el' => [ + 'isoName' => 'Greek (modern)', + 'nativeName' => 'ελληνικά', + ], + 'gn' => [ + 'isoName' => 'Guaraní', + 'nativeName' => 'Avañe\'ẽ', + ], + 'gu' => [ + 'isoName' => 'Gujarati', + 'nativeName' => 'ગુજરાતી', + ], + 'ht' => [ + 'isoName' => 'Haitian, Haitian Creole', + 'nativeName' => 'Kreyòl ayisyen', + ], + 'ha' => [ + 'isoName' => 'Hausa', + 'nativeName' => '(Hausa) هَوُسَ', + ], + 'he' => [ + 'isoName' => 'Hebrew (modern)', + 'nativeName' => 'עברית', + ], + 'hz' => [ + 'isoName' => 'Herero', + 'nativeName' => 'Otjiherero', + ], + 'hi' => [ + 'isoName' => 'Hindi', + 'nativeName' => 'हिन्दी, हिंदी', + ], + 'ho' => [ + 'isoName' => 'Hiri Motu', + 'nativeName' => 'Hiri Motu', + ], + 'hu' => [ + 'isoName' => 'Hungarian', + 'nativeName' => 'magyar', + ], + 'ia' => [ + 'isoName' => 'Interlingua', + 'nativeName' => 'Interlingua', + ], + 'id' => [ + 'isoName' => 'Indonesian', + 'nativeName' => 'Bahasa Indonesia', + ], + 'ie' => [ + 'isoName' => 'Interlingue', + 'nativeName' => 'Originally called Occidental; then Interlingue after WWII', + ], + 'ga' => [ + 'isoName' => 'Irish', + 'nativeName' => 'Gaeilge', + ], + 'ig' => [ + 'isoName' => 'Igbo', + 'nativeName' => 'Asụsụ Igbo', + ], + 'ik' => [ + 'isoName' => 'Inupiaq', + 'nativeName' => 'Iñupiaq, Iñupiatun', + ], + 'io' => [ + 'isoName' => 'Ido', + 'nativeName' => 'Ido', + ], + 'is' => [ + 'isoName' => 'Icelandic', + 'nativeName' => 'Íslenska', + ], + 'it' => [ + 'isoName' => 'Italian', + 'nativeName' => 'Italiano', + ], + 'iu' => [ + 'isoName' => 'Inuktitut', + 'nativeName' => 'ᐃᓄᒃᑎᑐᑦ', + ], + 'ja' => [ + 'isoName' => 'Japanese', + 'nativeName' => '日本語 (にほんご)', + ], + 'jv' => [ + 'isoName' => 'Javanese', + 'nativeName' => 'ꦧꦱꦗꦮ, Basa Jawa', + ], + 'kl' => [ + 'isoName' => 'Kalaallisut, Greenlandic', + 'nativeName' => 'kalaallisut, kalaallit oqaasii', + ], + 'kn' => [ + 'isoName' => 'Kannada', + 'nativeName' => 'ಕನ್ನಡ', + ], + 'kr' => [ + 'isoName' => 'Kanuri', + 'nativeName' => 'Kanuri', + ], + 'ks' => [ + 'isoName' => 'Kashmiri', + 'nativeName' => 'कश्मीरी, كشميري‎', + ], + 'kk' => [ + 'isoName' => 'Kazakh', + 'nativeName' => 'қазақ тілі', + ], + 'km' => [ + 'isoName' => 'Central Khmer', + 'nativeName' => 'ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ', + ], + 'ki' => [ + 'isoName' => 'Kikuyu, Gikuyu', + 'nativeName' => 'Gĩkũyũ', + ], + 'rw' => [ + 'isoName' => 'Kinyarwanda', + 'nativeName' => 'Ikinyarwanda', + ], + 'ky' => [ + 'isoName' => 'Kirghiz, Kyrgyz', + 'nativeName' => 'Кыргызча, Кыргыз тили', + ], + 'kv' => [ + 'isoName' => 'Komi', + 'nativeName' => 'коми кыв', + ], + 'kg' => [ + 'isoName' => 'Kongo', + 'nativeName' => 'Kikongo', + ], + 'ko' => [ + 'isoName' => 'Korean', + 'nativeName' => '한국어', + ], + 'ku' => [ + 'isoName' => 'Kurdish', + 'nativeName' => 'Kurdî, کوردی‎', + ], + 'kj' => [ + 'isoName' => 'Kuanyama, Kwanyama', + 'nativeName' => 'Kuanyama', + ], + 'la' => [ + 'isoName' => 'Latin', + 'nativeName' => 'latine, lingua latina', + ], + 'lb' => [ + 'isoName' => 'Luxembourgish, Letzeburgesch', + 'nativeName' => 'Lëtzebuergesch', + ], + 'lg' => [ + 'isoName' => 'Ganda', + 'nativeName' => 'Luganda', + ], + 'li' => [ + 'isoName' => 'Limburgan, Limburger, Limburgish', + 'nativeName' => 'Limburgs', + ], + 'ln' => [ + 'isoName' => 'Lingala', + 'nativeName' => 'Lingála', + ], + 'lo' => [ + 'isoName' => 'Lao', + 'nativeName' => 'ພາສາລາວ', + ], + 'lt' => [ + 'isoName' => 'Lithuanian', + 'nativeName' => 'lietuvių kalba', + ], + 'lu' => [ + 'isoName' => 'Luba-Katanga', + 'nativeName' => 'Kiluba', + ], + 'lv' => [ + 'isoName' => 'Latvian', + 'nativeName' => 'latviešu valoda', + ], + 'gv' => [ + 'isoName' => 'Manx', + 'nativeName' => 'Gaelg, Gailck', + ], + 'mk' => [ + 'isoName' => 'Macedonian', + 'nativeName' => 'македонски јазик', + ], + 'mg' => [ + 'isoName' => 'Malagasy', + 'nativeName' => 'fiteny malagasy', + ], + 'ms' => [ + 'isoName' => 'Malay', + 'nativeName' => 'Bahasa Melayu, بهاس ملايو‎', + ], + 'ml' => [ + 'isoName' => 'Malayalam', + 'nativeName' => 'മലയാളം', + ], + 'mt' => [ + 'isoName' => 'Maltese', + 'nativeName' => 'Malti', + ], + 'mi' => [ + 'isoName' => 'Maori', + 'nativeName' => 'te reo Māori', + ], + 'mr' => [ + 'isoName' => 'Marathi', + 'nativeName' => 'मराठी', + ], + 'mh' => [ + 'isoName' => 'Marshallese', + 'nativeName' => 'Kajin M̧ajeļ', + ], + 'mn' => [ + 'isoName' => 'Mongolian', + 'nativeName' => 'Монгол хэл', + ], + 'na' => [ + 'isoName' => 'Nauru', + 'nativeName' => 'Dorerin Naoero', + ], + 'nv' => [ + 'isoName' => 'Navajo, Navaho', + 'nativeName' => 'Diné bizaad', + ], + 'nd' => [ + 'isoName' => 'North Ndebele', + 'nativeName' => 'isiNdebele', + ], + 'ne' => [ + 'isoName' => 'Nepali', + 'nativeName' => 'नेपाली', + ], + 'ng' => [ + 'isoName' => 'Ndonga', + 'nativeName' => 'Owambo', + ], + 'nb' => [ + 'isoName' => 'Norwegian Bokmål', + 'nativeName' => 'Norsk Bokmål', + ], + 'nn' => [ + 'isoName' => 'Norwegian Nynorsk', + 'nativeName' => 'Norsk Nynorsk', + ], + 'no' => [ + 'isoName' => 'Norwegian', + 'nativeName' => 'Norsk', + ], + 'ii' => [ + 'isoName' => 'Sichuan Yi, Nuosu', + 'nativeName' => 'ꆈꌠ꒿ Nuosuhxop', + ], + 'nr' => [ + 'isoName' => 'South Ndebele', + 'nativeName' => 'isiNdebele', + ], + 'oc' => [ + 'isoName' => 'Occitan', + 'nativeName' => 'occitan, lenga d\'òc', + ], + 'oj' => [ + 'isoName' => 'Ojibwa', + 'nativeName' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ', + ], + 'cu' => [ + 'isoName' => 'Church Slavic, Church Slavonic, Old Church Slavonic, Old Slavonic, Old Bulgarian', + 'nativeName' => 'ѩзыкъ словѣньскъ', + ], + 'om' => [ + 'isoName' => 'Oromo', + 'nativeName' => 'Afaan Oromoo', + ], + 'or' => [ + 'isoName' => 'Oriya', + 'nativeName' => 'ଓଡ଼ିଆ', + ], + 'os' => [ + 'isoName' => 'Ossetian, Ossetic', + 'nativeName' => 'ирон æвзаг', + ], + 'pa' => [ + 'isoName' => 'Panjabi, Punjabi', + 'nativeName' => 'ਪੰਜਾਬੀ', + ], + 'pi' => [ + 'isoName' => 'Pali', + 'nativeName' => 'पाऴि', + ], + 'fa' => [ + 'isoName' => 'Persian', + 'nativeName' => 'فارسی', + ], + 'pl' => [ + 'isoName' => 'Polish', + 'nativeName' => 'język polski, polszczyzna', + ], + 'ps' => [ + 'isoName' => 'Pashto, Pushto', + 'nativeName' => 'پښتو', + ], + 'pt' => [ + 'isoName' => 'Portuguese', + 'nativeName' => 'Português', + ], + 'qu' => [ + 'isoName' => 'Quechua', + 'nativeName' => 'Runa Simi, Kichwa', + ], + 'rm' => [ + 'isoName' => 'Romansh', + 'nativeName' => 'Rumantsch Grischun', + ], + 'rn' => [ + 'isoName' => 'Rundi', + 'nativeName' => 'Ikirundi', + ], + 'ro' => [ + 'isoName' => 'Romanian, Moldavian, Moldovan', + 'nativeName' => 'Română', + ], + 'ru' => [ + 'isoName' => 'Russian', + 'nativeName' => 'русский', + ], + 'sa' => [ + 'isoName' => 'Sanskrit', + 'nativeName' => 'संस्कृतम्', + ], + 'sc' => [ + 'isoName' => 'Sardinian', + 'nativeName' => 'sardu', + ], + 'sd' => [ + 'isoName' => 'Sindhi', + 'nativeName' => 'सिन्धी, سنڌي، سندھی‎', + ], + 'se' => [ + 'isoName' => 'Northern Sami', + 'nativeName' => 'Davvisámegiella', + ], + 'sm' => [ + 'isoName' => 'Samoan', + 'nativeName' => 'gagana fa\'a Samoa', + ], + 'sg' => [ + 'isoName' => 'Sango', + 'nativeName' => 'yângâ tî sängö', + ], + 'sr' => [ + 'isoName' => 'Serbian', + 'nativeName' => 'српски језик', + ], + 'gd' => [ + 'isoName' => 'Gaelic, Scottish Gaelic', + 'nativeName' => 'Gàidhlig', + ], + 'sn' => [ + 'isoName' => 'Shona', + 'nativeName' => 'chiShona', + ], + 'si' => [ + 'isoName' => 'Sinhala, Sinhalese', + 'nativeName' => 'සිංහල', + ], + 'sk' => [ + 'isoName' => 'Slovak', + 'nativeName' => 'Slovenčina, Slovenský Jazyk', + ], + 'sl' => [ + 'isoName' => 'Slovene', + 'nativeName' => 'Slovenski Jezik, Slovenščina', + ], + 'so' => [ + 'isoName' => 'Somali', + 'nativeName' => 'Soomaaliga, af Soomaali', + ], + 'st' => [ + 'isoName' => 'Southern Sotho', + 'nativeName' => 'Sesotho', + ], + 'es' => [ + 'isoName' => 'Spanish, Castilian', + 'nativeName' => 'Español', + ], + 'su' => [ + 'isoName' => 'Sundanese', + 'nativeName' => 'Basa Sunda', + ], + 'sw' => [ + 'isoName' => 'Swahili', + 'nativeName' => 'Kiswahili', + ], + 'ss' => [ + 'isoName' => 'Swati', + 'nativeName' => 'SiSwati', + ], + 'sv' => [ + 'isoName' => 'Swedish', + 'nativeName' => 'Svenska', + ], + 'ta' => [ + 'isoName' => 'Tamil', + 'nativeName' => 'தமிழ்', + ], + 'te' => [ + 'isoName' => 'Telugu', + 'nativeName' => 'తెలుగు', + ], + 'tg' => [ + 'isoName' => 'Tajik', + 'nativeName' => 'тоҷикӣ, toçikī, تاجیکی‎', + ], + 'th' => [ + 'isoName' => 'Thai', + 'nativeName' => 'ไทย', + ], + 'ti' => [ + 'isoName' => 'Tigrinya', + 'nativeName' => 'ትግርኛ', + ], + 'bo' => [ + 'isoName' => 'Tibetan', + 'nativeName' => 'བོད་ཡིག', + ], + 'tk' => [ + 'isoName' => 'Turkmen', + 'nativeName' => 'Türkmen, Түркмен', + ], + 'tl' => [ + 'isoName' => 'Tagalog', + 'nativeName' => 'Wikang Tagalog', + ], + 'tn' => [ + 'isoName' => 'Tswana', + 'nativeName' => 'Setswana', + ], + 'to' => [ + 'isoName' => 'Tongan (Tonga Islands)', + 'nativeName' => 'Faka Tonga', + ], + 'tr' => [ + 'isoName' => 'Turkish', + 'nativeName' => 'Türkçe', + ], + 'ts' => [ + 'isoName' => 'Tsonga', + 'nativeName' => 'Xitsonga', + ], + 'tt' => [ + 'isoName' => 'Tatar', + 'nativeName' => 'татар теле, tatar tele', + ], + 'tw' => [ + 'isoName' => 'Twi', + 'nativeName' => 'Twi', + ], + 'ty' => [ + 'isoName' => 'Tahitian', + 'nativeName' => 'Reo Tahiti', + ], + 'ug' => [ + 'isoName' => 'Uighur, Uyghur', + 'nativeName' => 'Uyƣurqə, ‫ئۇيغۇرچ', + ], + 'uk' => [ + 'isoName' => 'Ukrainian', + 'nativeName' => 'Українська', + ], + 'ur' => [ + 'isoName' => 'Urdu', + 'nativeName' => 'اردو', + ], + 'uz' => [ + 'isoName' => 'Uzbek', + 'nativeName' => 'Oʻzbek, Ўзбек, أۇزبېك‎', + ], + 've' => [ + 'isoName' => 'Venda', + 'nativeName' => 'Tshivenḓa', + ], + 'vi' => [ + 'isoName' => 'Vietnamese', + 'nativeName' => 'Tiếng Việt', + ], + 'vo' => [ + 'isoName' => 'Volapük', + 'nativeName' => 'Volapük', + ], + 'wa' => [ + 'isoName' => 'Walloon', + 'nativeName' => 'Walon', + ], + 'cy' => [ + 'isoName' => 'Welsh', + 'nativeName' => 'Cymraeg', + ], + 'wo' => [ + 'isoName' => 'Wolof', + 'nativeName' => 'Wollof', + ], + 'fy' => [ + 'isoName' => 'Western Frisian', + 'nativeName' => 'Frysk', + ], + 'xh' => [ + 'isoName' => 'Xhosa', + 'nativeName' => 'isiXhosa', + ], + 'yi' => [ + 'isoName' => 'Yiddish', + 'nativeName' => 'ייִדיש', + ], + 'yo' => [ + 'isoName' => 'Yoruba', + 'nativeName' => 'Yorùbá', + ], + 'za' => [ + 'isoName' => 'Zhuang, Chuang', + 'nativeName' => 'Saɯ cueŋƅ, Saw cuengh', + ], + 'zu' => [ + 'isoName' => 'Zulu', + 'nativeName' => 'isiZulu', + ], + /* + * Add ISO 639-3 languages available in EDD\Vendor\Carbon + */ + 'agq' => [ + 'isoName' => 'Aghem', + 'nativeName' => 'Aghem', + ], + 'agr' => [ + 'isoName' => 'Aguaruna', + 'nativeName' => 'Aguaruna', + ], + 'anp' => [ + 'isoName' => 'Angika', + 'nativeName' => 'Angika', + ], + 'asa' => [ + 'isoName' => 'Asu', + 'nativeName' => 'Asu', + ], + 'ast' => [ + 'isoName' => 'Asturian', + 'nativeName' => 'Asturian', + ], + 'ayc' => [ + 'isoName' => 'Southern Aymara', + 'nativeName' => 'Southern Aymara', + ], + 'bas' => [ + 'isoName' => 'Basaa', + 'nativeName' => 'Basaa', + ], + 'bem' => [ + 'isoName' => 'Bemba', + 'nativeName' => 'Bemba', + ], + 'bez' => [ + 'isoName' => 'Bena', + 'nativeName' => 'Bena', + ], + 'bhb' => [ + 'isoName' => 'Bhili', + 'nativeName' => 'Bhili', + ], + 'bho' => [ + 'isoName' => 'Bhojpuri', + 'nativeName' => 'Bhojpuri', + ], + 'brx' => [ + 'isoName' => 'Bodo', + 'nativeName' => 'Bodo', + ], + 'byn' => [ + 'isoName' => 'Bilin', + 'nativeName' => 'Bilin', + ], + 'ccp' => [ + 'isoName' => 'Chakma', + 'nativeName' => 'Chakma', + ], + 'cgg' => [ + 'isoName' => 'Chiga', + 'nativeName' => 'Chiga', + ], + 'chr' => [ + 'isoName' => 'Cherokee', + 'nativeName' => 'Cherokee', + ], + 'cmn' => [ + 'isoName' => 'Chinese', + 'nativeName' => 'Chinese', + ], + 'crh' => [ + 'isoName' => 'Crimean Turkish', + 'nativeName' => 'Crimean Turkish', + ], + 'csb' => [ + 'isoName' => 'Kashubian', + 'nativeName' => 'Kashubian', + ], + 'dav' => [ + 'isoName' => 'Taita', + 'nativeName' => 'Taita', + ], + 'dje' => [ + 'isoName' => 'Zarma', + 'nativeName' => 'Zarma', + ], + 'doi' => [ + 'isoName' => 'Dogri (macrolanguage)', + 'nativeName' => 'Dogri (macrolanguage)', + ], + 'dsb' => [ + 'isoName' => 'Lower Sorbian', + 'nativeName' => 'Lower Sorbian', + ], + 'dua' => [ + 'isoName' => 'Duala', + 'nativeName' => 'Duala', + ], + 'dyo' => [ + 'isoName' => 'Jola-Fonyi', + 'nativeName' => 'Jola-Fonyi', + ], + 'ebu' => [ + 'isoName' => 'Embu', + 'nativeName' => 'Embu', + ], + 'ewo' => [ + 'isoName' => 'Ewondo', + 'nativeName' => 'Ewondo', + ], + 'fil' => [ + 'isoName' => 'Filipino', + 'nativeName' => 'Filipino', + ], + 'fur' => [ + 'isoName' => 'Friulian', + 'nativeName' => 'Friulian', + ], + 'gez' => [ + 'isoName' => 'Geez', + 'nativeName' => 'Geez', + ], + 'gom' => [ + 'isoName' => 'Konkani, Goan', + 'nativeName' => 'ಕೊಂಕಣಿ', + ], + 'gsw' => [ + 'isoName' => 'Swiss German', + 'nativeName' => 'Swiss German', + ], + 'guz' => [ + 'isoName' => 'Gusii', + 'nativeName' => 'Gusii', + ], + 'hak' => [ + 'isoName' => 'Hakka Chinese', + 'nativeName' => 'Hakka Chinese', + ], + 'haw' => [ + 'isoName' => 'Hawaiian', + 'nativeName' => 'Hawaiian', + ], + 'hif' => [ + 'isoName' => 'Fiji Hindi', + 'nativeName' => 'Fiji Hindi', + ], + 'hne' => [ + 'isoName' => 'Chhattisgarhi', + 'nativeName' => 'Chhattisgarhi', + ], + 'hsb' => [ + 'isoName' => 'Upper Sorbian', + 'nativeName' => 'Upper Sorbian', + ], + 'jgo' => [ + 'isoName' => 'Ngomba', + 'nativeName' => 'Ngomba', + ], + 'jmc' => [ + 'isoName' => 'Machame', + 'nativeName' => 'Machame', + ], + 'kab' => [ + 'isoName' => 'Kabyle', + 'nativeName' => 'Kabyle', + ], + 'kam' => [ + 'isoName' => 'Kamba', + 'nativeName' => 'Kamba', + ], + 'kde' => [ + 'isoName' => 'Makonde', + 'nativeName' => 'Makonde', + ], + 'kea' => [ + 'isoName' => 'Kabuverdianu', + 'nativeName' => 'Kabuverdianu', + ], + 'khq' => [ + 'isoName' => 'Koyra Chiini', + 'nativeName' => 'Koyra Chiini', + ], + 'kkj' => [ + 'isoName' => 'Kako', + 'nativeName' => 'Kako', + ], + 'kln' => [ + 'isoName' => 'Kalenjin', + 'nativeName' => 'Kalenjin', + ], + 'kok' => [ + 'isoName' => 'Konkani', + 'nativeName' => 'Konkani', + ], + 'ksb' => [ + 'isoName' => 'Shambala', + 'nativeName' => 'Shambala', + ], + 'ksf' => [ + 'isoName' => 'Bafia', + 'nativeName' => 'Bafia', + ], + 'ksh' => [ + 'isoName' => 'Colognian', + 'nativeName' => 'Colognian', + ], + 'lag' => [ + 'isoName' => 'Langi', + 'nativeName' => 'Langi', + ], + 'lij' => [ + 'isoName' => 'Ligurian', + 'nativeName' => 'Ligurian', + ], + 'lkt' => [ + 'isoName' => 'Lakota', + 'nativeName' => 'Lakota', + ], + 'lrc' => [ + 'isoName' => 'Northern Luri', + 'nativeName' => 'Northern Luri', + ], + 'luo' => [ + 'isoName' => 'Luo', + 'nativeName' => 'Luo', + ], + 'luy' => [ + 'isoName' => 'Luyia', + 'nativeName' => 'Luyia', + ], + 'lzh' => [ + 'isoName' => 'Literary Chinese', + 'nativeName' => 'Literary Chinese', + ], + 'mag' => [ + 'isoName' => 'Magahi', + 'nativeName' => 'Magahi', + ], + 'mai' => [ + 'isoName' => 'Maithili', + 'nativeName' => 'Maithili', + ], + 'mas' => [ + 'isoName' => 'Masai', + 'nativeName' => 'Masai', + ], + 'mer' => [ + 'isoName' => 'Meru', + 'nativeName' => 'Meru', + ], + 'mfe' => [ + 'isoName' => 'Morisyen', + 'nativeName' => 'Morisyen', + ], + 'mgh' => [ + 'isoName' => 'Makhuwa-Meetto', + 'nativeName' => 'Makhuwa-Meetto', + ], + 'mgo' => [ + 'isoName' => 'Metaʼ', + 'nativeName' => 'Metaʼ', + ], + 'mhr' => [ + 'isoName' => 'Eastern Mari', + 'nativeName' => 'Eastern Mari', + ], + 'miq' => [ + 'isoName' => 'Mískito', + 'nativeName' => 'Mískito', + ], + 'mjw' => [ + 'isoName' => 'Karbi', + 'nativeName' => 'Karbi', + ], + 'mni' => [ + 'isoName' => 'Manipuri', + 'nativeName' => 'Manipuri', + ], + 'mua' => [ + 'isoName' => 'Mundang', + 'nativeName' => 'Mundang', + ], + 'mzn' => [ + 'isoName' => 'Mazanderani', + 'nativeName' => 'Mazanderani', + ], + 'nan' => [ + 'isoName' => 'Min Nan Chinese', + 'nativeName' => 'Min Nan Chinese', + ], + 'naq' => [ + 'isoName' => 'Nama', + 'nativeName' => 'Nama', + ], + 'nds' => [ + 'isoName' => 'Low German', + 'nativeName' => 'Low German', + ], + 'nhn' => [ + 'isoName' => 'Central Nahuatl', + 'nativeName' => 'Central Nahuatl', + ], + 'niu' => [ + 'isoName' => 'Niuean', + 'nativeName' => 'Niuean', + ], + 'nmg' => [ + 'isoName' => 'Kwasio', + 'nativeName' => 'Kwasio', + ], + 'nnh' => [ + 'isoName' => 'Ngiemboon', + 'nativeName' => 'Ngiemboon', + ], + 'nso' => [ + 'isoName' => 'Northern Sotho', + 'nativeName' => 'Northern Sotho', + ], + 'nus' => [ + 'isoName' => 'Nuer', + 'nativeName' => 'Nuer', + ], + 'nyn' => [ + 'isoName' => 'Nyankole', + 'nativeName' => 'Nyankole', + ], + 'pap' => [ + 'isoName' => 'Papiamento', + 'nativeName' => 'Papiamento', + ], + 'prg' => [ + 'isoName' => 'Prussian', + 'nativeName' => 'Prussian', + ], + 'quz' => [ + 'isoName' => 'Cusco Quechua', + 'nativeName' => 'Cusco Quechua', + ], + 'raj' => [ + 'isoName' => 'Rajasthani', + 'nativeName' => 'Rajasthani', + ], + 'rof' => [ + 'isoName' => 'Rombo', + 'nativeName' => 'Rombo', + ], + 'rwk' => [ + 'isoName' => 'Rwa', + 'nativeName' => 'Rwa', + ], + 'sah' => [ + 'isoName' => 'Sakha', + 'nativeName' => 'Sakha', + ], + 'saq' => [ + 'isoName' => 'Samburu', + 'nativeName' => 'Samburu', + ], + 'sat' => [ + 'isoName' => 'Santali', + 'nativeName' => 'Santali', + ], + 'sbp' => [ + 'isoName' => 'Sangu', + 'nativeName' => 'Sangu', + ], + 'scr' => [ + 'isoName' => 'Serbo Croatian', + 'nativeName' => 'Serbo Croatian', + ], + 'seh' => [ + 'isoName' => 'Sena', + 'nativeName' => 'Sena', + ], + 'ses' => [ + 'isoName' => 'Koyraboro Senni', + 'nativeName' => 'Koyraboro Senni', + ], + 'sgs' => [ + 'isoName' => 'Samogitian', + 'nativeName' => 'Samogitian', + ], + 'shi' => [ + 'isoName' => 'Tachelhit', + 'nativeName' => 'Tachelhit', + ], + 'shn' => [ + 'isoName' => 'Shan', + 'nativeName' => 'Shan', + ], + 'shs' => [ + 'isoName' => 'Shuswap', + 'nativeName' => 'Shuswap', + ], + 'sid' => [ + 'isoName' => 'Sidamo', + 'nativeName' => 'Sidamo', + ], + 'smn' => [ + 'isoName' => 'Inari Sami', + 'nativeName' => 'Inari Sami', + ], + 'szl' => [ + 'isoName' => 'Silesian', + 'nativeName' => 'Silesian', + ], + 'tcy' => [ + 'isoName' => 'Tulu', + 'nativeName' => 'Tulu', + ], + 'teo' => [ + 'isoName' => 'Teso', + 'nativeName' => 'Teso', + ], + 'tet' => [ + 'isoName' => 'Tetum', + 'nativeName' => 'Tetum', + ], + 'the' => [ + 'isoName' => 'Chitwania Tharu', + 'nativeName' => 'Chitwania Tharu', + ], + 'tig' => [ + 'isoName' => 'Tigre', + 'nativeName' => 'Tigre', + ], + 'tlh' => [ + 'isoName' => 'Klingon', + 'nativeName' => 'tlhIngan Hol', + ], + 'tpi' => [ + 'isoName' => 'Tok Pisin', + 'nativeName' => 'Tok Pisin', + ], + 'twq' => [ + 'isoName' => 'Tasawaq', + 'nativeName' => 'Tasawaq', + ], + 'tzl' => [ + 'isoName' => 'Talossan', + 'nativeName' => 'Talossan', + ], + 'tzm' => [ + 'isoName' => 'Tamazight, Central Atlas', + 'nativeName' => 'ⵜⵎⴰⵣⵉⵖⵜ', + ], + 'unm' => [ + 'isoName' => 'Unami', + 'nativeName' => 'Unami', + ], + 'vai' => [ + 'isoName' => 'Vai', + 'nativeName' => 'Vai', + ], + 'vun' => [ + 'isoName' => 'Vunjo', + 'nativeName' => 'Vunjo', + ], + 'wae' => [ + 'isoName' => 'Walser', + 'nativeName' => 'Walser', + ], + 'wal' => [ + 'isoName' => 'Wolaytta', + 'nativeName' => 'Wolaytta', + ], + 'xog' => [ + 'isoName' => 'Soga', + 'nativeName' => 'Soga', + ], + 'yav' => [ + 'isoName' => 'Yangben', + 'nativeName' => 'Yangben', + ], + 'yue' => [ + 'isoName' => 'Cantonese', + 'nativeName' => 'Cantonese', + ], + 'yuw' => [ + 'isoName' => 'Yau (Morobe Province)', + 'nativeName' => 'Yau (Morobe Province)', + ], + 'zgh' => [ + 'isoName' => 'Standard Moroccan Tamazight', + 'nativeName' => 'Standard Moroccan Tamazight', + ], +]; diff --git a/libraries/Carbon/src/Carbon/List/regions.php b/libraries/Carbon/src/Carbon/List/regions.php new file mode 100644 index 00000000000..8f7f4dc7d0d --- /dev/null +++ b/libraries/Carbon/src/Carbon/List/regions.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * ISO 3166-2 + */ +return [ + 'AD' => 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia (Plurinational State of)', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => 'Côte d\'Ivoire', + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cabo Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czechia', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia (Federated States of)', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom of Great Britain and Northern Ireland', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran (Islamic Republic of)', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => 'Korea (Democratic People\'s Republic of)', + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => 'Lao People\'s Democratic Republic', + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Eswatini', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States of America', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela (Bolivarian Republic of)', + 'VG' => 'Virgin Islands (British)', + 'VI' => 'Virgin Islands (U.S.)', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/libraries/Carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php b/libraries/Carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php new file mode 100644 index 00000000000..b11214b258a --- /dev/null +++ b/libraries/Carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\MessageFormatter; + +use ReflectionMethod; +use EDD\Vendor\Symfony\Component\Translation\Formatter\MessageFormatter; +use EDD\Vendor\Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +// @codeCoverageIgnoreStart +$transMethod = new ReflectionMethod(MessageFormatterInterface::class, 'format'); + +require $transMethod->getParameters()[0]->hasType() + ? __DIR__.'/../../../lazy/Carbon/MessageFormatter/MessageFormatterMapperStrongType.php' + : __DIR__.'/../../../lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php'; +// @codeCoverageIgnoreEnd + +final class MessageFormatterMapper extends LazyMessageFormatter +{ + /** + * Wrapped formatter. + * + * @var MessageFormatterInterface + */ + protected $formatter; + + public function __construct(?MessageFormatterInterface $formatter = null) + { + $this->formatter = $formatter ?? new MessageFormatter(); + } + + protected function transformLocale(?string $locale): ?string + { + return $locale ? preg_replace('/[_@][A-Za-z][a-z]{2,}/', '', $locale) : $locale; + } +} diff --git a/libraries/Carbon/src/Carbon/PHPStan/AbstractMacro.php b/libraries/Carbon/src/Carbon/PHPStan/AbstractMacro.php new file mode 100644 index 00000000000..a2ef1a8ead2 --- /dev/null +++ b/libraries/Carbon/src/Carbon/PHPStan/AbstractMacro.php @@ -0,0 +1,286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\PHPStan; + +use Closure; +use InvalidArgumentException; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter as AdapterReflectionParameter; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType as AdapterReflectionType; +use PHPStan\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; +use PHPStan\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction; +use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter; +use PHPStan\Reflection\Php\BuiltinMethodReflection; +use PHPStan\TrinaryLogic; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use ReflectionParameter; +use ReflectionType; +use stdClass; +use Throwable; + +abstract class AbstractMacro implements BuiltinMethodReflection +{ + /** + * The reflection function/method. + * + * @var ReflectionFunction|ReflectionMethod + */ + protected $reflectionFunction; + + /** + * The class name. + * + * @var class-string + */ + private $className; + + /** + * The method name. + * + * @var string + */ + private $methodName; + + /** + * The parameters. + * + * @var ReflectionParameter[] + */ + private $parameters; + + /** + * The is static. + * + * @var bool + */ + private $static = false; + + /** + * Macro constructor. + * + * @param class-string $className + * @param string $methodName + * @param callable $macro + */ + public function __construct(string $className, string $methodName, $macro) + { + $this->className = $className; + $this->methodName = $methodName; + $rawReflectionFunction = \is_array($macro) + ? new ReflectionMethod($macro[0], $macro[1]) + : new ReflectionFunction($macro); + $this->reflectionFunction = self::hasModernParser() + ? $this->getReflectionFunction($macro) + : $rawReflectionFunction; // @codeCoverageIgnore + $this->parameters = array_map( + function ($parameter) { + if ($parameter instanceof BetterReflectionParameter) { + return new AdapterReflectionParameter($parameter); + } + + return $parameter; // @codeCoverageIgnore + }, + $this->reflectionFunction->getParameters() + ); + + if ($rawReflectionFunction->isClosure()) { + try { + $closure = $rawReflectionFunction->getClosure(); + $boundClosure = Closure::bind($closure, new stdClass()); + $this->static = (!$boundClosure || (new ReflectionFunction($boundClosure))->getClosureThis() === null); + } catch (Throwable $e) { + $this->static = true; + } + } + } + + private function getReflectionFunction($spec) + { + if (\is_array($spec) && \count($spec) === 2 && \is_string($spec[1])) { + \assert($spec[1] !== ''); + + if (\is_object($spec[0])) { + return BetterReflectionClass::createFromInstance($spec[0]) + ->getMethod($spec[1]); + } + + return BetterReflectionClass::createFromName($spec[0]) + ->getMethod($spec[1]); + } + + if (\is_string($spec)) { + return BetterReflectionFunction::createFromName($spec); + } + + if ($spec instanceof Closure) { + return BetterReflectionFunction::createFromClosure($spec); + } + + throw new InvalidArgumentException('Could not create reflection from the spec given'); // @codeCoverageIgnore + } + + /** + * {@inheritdoc} + */ + public function getDeclaringClass(): ReflectionClass + { + return new ReflectionClass($this->className); + } + + /** + * {@inheritdoc} + */ + public function isPrivate(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isPublic(): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isFinal(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isInternal(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isAbstract(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isStatic(): bool + { + return $this->static; + } + + /** + * {@inheritdoc} + */ + public function getDocComment(): ?string + { + return $this->reflectionFunction->getDocComment() ?: null; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->methodName; + } + + /** + * {@inheritdoc} + */ + public function getParameters(): array + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function getReturnType(): ?ReflectionType + { + $type = $this->reflectionFunction->getReturnType(); + + if ($type instanceof ReflectionType) { + return $type; // @codeCoverageIgnore + } + + return self::adaptType($type); + } + + /** + * {@inheritdoc} + */ + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean( + $this->reflectionFunction->isDeprecated() || + preg_match('/@deprecated/i', $this->getDocComment() ?: '') + ); + } + + /** + * {@inheritdoc} + */ + public function isVariadic(): bool + { + return $this->reflectionFunction->isVariadic(); + } + + /** + * {@inheritdoc} + */ + public function getPrototype(): BuiltinMethodReflection + { + return $this; + } + + public function getTentativeReturnType(): ?ReflectionType + { + return null; + } + + public function returnsByReference(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + private static function adaptType($type) + { + $method = method_exists(AdapterReflectionType::class, 'fromTypeOrNull') + ? 'fromTypeOrNull' + : 'fromReturnTypeOrNull'; // @codeCoverageIgnore + + return AdapterReflectionType::$method($type); + } + + private static function hasModernParser(): bool + { + static $modernParser = null; + + if ($modernParser !== null) { + return $modernParser; + } + + $modernParser = method_exists(AdapterReflectionType::class, 'fromTypeOrNull'); + + return $modernParser; + } +} diff --git a/libraries/Carbon/src/Carbon/PHPStan/Macro.php b/libraries/Carbon/src/Carbon/PHPStan/Macro.php new file mode 100644 index 00000000000..22d0c34960e --- /dev/null +++ b/libraries/Carbon/src/Carbon/PHPStan/Macro.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection\Adapter; +use PHPStan\Reflection\Php\BuiltinMethodReflection; +use ReflectionMethod; + +$method = new ReflectionMethod(BuiltinMethodReflection::class, 'getReflection'); + +require $method->hasReturnType() && $method->getReturnType()->getName() === Adapter\ReflectionMethod::class + ? __DIR__.'/../../../lazy/Carbon/PHPStan/AbstractMacroStatic.php' + : __DIR__.'/../../../lazy/Carbon/PHPStan/AbstractMacroBuiltin.php'; + +$method = new ReflectionMethod(BuiltinMethodReflection::class, 'getFileName'); + +require $method->hasReturnType() + ? __DIR__.'/../../../lazy/Carbon/PHPStan/MacroStrongType.php' + : __DIR__.'/../../../lazy/Carbon/PHPStan/MacroWeakType.php'; + +final class Macro extends LazyMacro +{ +} diff --git a/libraries/Carbon/src/Carbon/PHPStan/MacroExtension.php b/libraries/Carbon/src/Carbon/PHPStan/MacroExtension.php new file mode 100644 index 00000000000..7cc22d95b65 --- /dev/null +++ b/libraries/Carbon/src/Carbon/PHPStan/MacroExtension.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\PHPStan; + +use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\MethodsClassReflectionExtension; +use PHPStan\Reflection\Php\PhpMethodReflectionFactory; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\TypehintHelper; + +/** + * Class MacroExtension. + * + * @codeCoverageIgnore Pure PHPStan wrapper. + */ +final class MacroExtension implements MethodsClassReflectionExtension +{ + /** + * @var PhpMethodReflectionFactory + */ + protected $methodReflectionFactory; + + /** + * @var MacroScanner + */ + protected $scanner; + + /** + * Extension constructor. + * + * @param PhpMethodReflectionFactory $methodReflectionFactory + * @param ReflectionProvider $reflectionProvider + */ + public function __construct( + PhpMethodReflectionFactory $methodReflectionFactory, + ReflectionProvider $reflectionProvider + ) { + $this->scanner = new MacroScanner($reflectionProvider); + $this->methodReflectionFactory = $methodReflectionFactory; + } + + /** + * {@inheritdoc} + */ + public function hasMethod(ClassReflection $classReflection, string $methodName): bool + { + return $this->scanner->hasMethod($classReflection->getName(), $methodName); + } + + /** + * {@inheritdoc} + */ + public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + $builtinMacro = $this->scanner->getMethod($classReflection->getName(), $methodName); + $supportAssertions = class_exists(Assertions::class); + + return $this->methodReflectionFactory->create( + $classReflection, + null, + $builtinMacro, + $classReflection->getActiveTemplateTypeMap(), + [], + TypehintHelper::decideTypeFromReflection($builtinMacro->getReturnType()), + null, + null, + $builtinMacro->isDeprecated()->yes(), + $builtinMacro->isInternal(), + $builtinMacro->isFinal(), + $supportAssertions ? null : $builtinMacro->getDocComment(), + $supportAssertions ? Assertions::createEmpty() : null, + null, + $builtinMacro->getDocComment(), + [] + ); + } +} diff --git a/libraries/Carbon/src/Carbon/PHPStan/MacroScanner.php b/libraries/Carbon/src/Carbon/PHPStan/MacroScanner.php new file mode 100644 index 00000000000..a01450ffa17 --- /dev/null +++ b/libraries/Carbon/src/Carbon/PHPStan/MacroScanner.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\PHPStan; + +use EDD\Vendor\Carbon\CarbonInterface; +use PHPStan\Reflection\ReflectionProvider; +use ReflectionClass; +use ReflectionException; + +final class MacroScanner +{ + /** + * @var \PHPStan\Reflection\ReflectionProvider + */ + private $reflectionProvider; + + /** + * MacroScanner constructor. + * + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + */ + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + /** + * Return true if the given pair class-method is a EDD\Vendor\Carbon macro. + * + * @param class-string $className + * @param string $methodName + * + * @return bool + */ + public function hasMethod(string $className, string $methodName): bool + { + $classReflection = $this->reflectionProvider->getClass($className); + + if ( + $classReflection->getName() !== CarbonInterface::class && + !$classReflection->isSubclassOf(CarbonInterface::class) + ) { + return false; + } + + return \is_callable([$className, 'hasMacro']) && + $className::hasMacro($methodName); + } + + /** + * Return the Macro for a given pair class-method. + * + * @param class-string $className + * @param string $methodName + * + * @throws ReflectionException + * + * @return Macro + */ + public function getMethod(string $className, string $methodName): Macro + { + $reflectionClass = new ReflectionClass($className); + $property = $reflectionClass->getProperty('globalMacros'); + + $property->setAccessible(true); + $macro = $property->getValue()[$methodName]; + + return new Macro( + $className, + $methodName, + $macro + ); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Boundaries.php b/libraries/Carbon/src/Carbon/Traits/Boundaries.php new file mode 100644 index 00000000000..842d0d6a376 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Boundaries.php @@ -0,0 +1,443 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\Exceptions\UnknownUnitException; + +/** + * Trait Boundaries. + * + * startOf, endOf and derived method for each unit. + * + * Depends on the following properties: + * + * @property int $year + * @property int $month + * @property int $daysInMonth + * @property int $quarter + * + * Depends on the following methods: + * + * @method $this setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0) + * @method $this setDate(int $year, int $month, int $day) + * @method $this addMonths(int $value = 1) + */ +trait Boundaries +{ + /** + * Resets the time to 00:00:00 start of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDay(); + * ``` + * + * @return static + */ + public function startOfDay() + { + return $this->setTime(0, 0, 0, 0); + } + + /** + * Resets the time to 23:59:59.999999 end of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDay(); + * ``` + * + * @return static + */ + public function endOfDay() + { + return $this->setTime(static::HOURS_PER_DAY - 1, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMonth(); + * ``` + * + * @return static + */ + public function startOfMonth() + { + return $this->setDate($this->year, $this->month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the month and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMonth(); + * ``` + * + * @return static + */ + public function endOfMonth() + { + return $this->setDate($this->year, $this->month, $this->daysInMonth)->endOfDay(); + } + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfQuarter(); + * ``` + * + * @return static + */ + public function startOfQuarter() + { + $month = ($this->quarter - 1) * static::MONTHS_PER_QUARTER + 1; + + return $this->setDate($this->year, $month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the quarter and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfQuarter(); + * ``` + * + * @return static + */ + public function endOfQuarter() + { + return $this->startOfQuarter()->addMonths(static::MONTHS_PER_QUARTER - 1)->endOfMonth(); + } + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfYear(); + * ``` + * + * @return static + */ + public function startOfYear() + { + return $this->setDate($this->year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the year and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfYear(); + * ``` + * + * @return static + */ + public function endOfYear() + { + return $this->setDate($this->year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDecade(); + * ``` + * + * @return static + */ + public function startOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the decade and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDecade(); + * ``` + * + * @return static + */ + public function endOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE + static::YEARS_PER_DECADE - 1; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfCentury(); + * ``` + * + * @return static + */ + public function startOfCentury() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_CENTURY; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the century and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfCentury(); + * ``` + * + * @return static + */ + public function endOfCentury() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_CENTURY + static::YEARS_PER_CENTURY; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the millennium and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMillennium(); + * ``` + * + * @return static + */ + public function startOfMillennium() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_MILLENNIUM; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the millennium and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMillennium(); + * ``` + * + * @return static + */ + public function endOfMillennium() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_MILLENNIUM + static::YEARS_PER_MILLENNIUM; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; + * ``` + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return static + */ + public function startOfWeek($weekStartsAt = null) + { + return $this->subDays((7 + $this->dayOfWeek - ($weekStartsAt ?? $this->firstWeekDay)) % 7)->startOfDay(); + } + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; + * ``` + * + * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * + * @return static + */ + public function endOfWeek($weekEndsAt = null) + { + return $this->addDays((7 - $this->dayOfWeek + ($weekEndsAt ?? $this->lastWeekDay)) % 7)->endOfDay(); + } + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); + * ``` + * + * @return static + */ + public function startOfHour() + { + return $this->setTime($this->hour, 0, 0, 0); + } + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); + * ``` + * + * @return static + */ + public function endOfHour() + { + return $this->setTime($this->hour, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current minute, seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); + * ``` + * + * @return static + */ + public function startOfMinute() + { + return $this->setTime($this->hour, $this->minute, 0, 0); + } + + /** + * Modify to end of current minute, seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); + * ``` + * + * @return static + */ + public function endOfMinute() + { + return $this->setTime($this->hour, $this->minute, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current second, microseconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function startOfSecond() + { + return $this->setTime($this->hour, $this->minute, $this->second, 0); + } + + /** + * Modify to end of current second, microseconds become 999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function endOfSecond() + { + return $this->setTime($this->hour, $this->minute, $this->second, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function startOf($unit, ...$params) + { + $ucfUnit = ucfirst(static::singularUnit($unit)); + $method = "startOf$ucfUnit"; + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method(...$params); + } + + /** + * Modify to end of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function endOf($unit, ...$params) + { + $ucfUnit = ucfirst(static::singularUnit($unit)); + $method = "endOf$ucfUnit"; + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method(...$params); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Cast.php b/libraries/Carbon/src/Carbon/Traits/Cast.php new file mode 100644 index 00000000000..c52a4da852b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Cast.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\Exceptions\InvalidCastException; +use DateTimeInterface; + +/** + * Trait Cast. + * + * Utils to cast into an other class. + */ +trait Cast +{ + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeInterface + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DateTimeInterface::class, true)) { + return new $className($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Comparison.php b/libraries/Carbon/src/Carbon/Traits/Comparison.php new file mode 100644 index 00000000000..ed33dbc270d --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Comparison.php @@ -0,0 +1,1129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use BadMethodCallException; +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\Exceptions\BadComparisonUnitException; +use InvalidArgumentException; + +/** + * Trait Comparison. + * + * Comparison utils and testers. All the following methods return booleans. + * nowWithSameTz + * + * Depends on the following methods: + * + * @method static resolveCarbon($date) + * @method static copy() + * @method static nowWithSameTz() + * @method static static yesterday($timezone = null) + * @method static static tomorrow($timezone = null) + */ +trait Comparison +{ + /** @var bool */ + protected $endOfTime = false; + + /** @var bool */ + protected $startOfTime = false; + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->eq(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see equalTo() + * + * @return bool + */ + public function eq($date): bool + { + return $this->equalTo($date); + } + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function equalTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this == $this->resolveCarbon($date); + } + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->ne(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($date): bool + { + return $this->notEqualTo($date); + } + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function notEqualTo($date): bool + { + return !$this->equalTo($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function gt($date): bool + { + return $this->greaterThan($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThan($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this > $this->resolveCarbon($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function isAfter($date): bool + { + return $this->greaterThan($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($date): bool + { + return $this->greaterThanOrEqualTo($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThanOrEqualTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this >= $this->resolveCarbon($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function lt($date): bool + { + return $this->lessThan($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThan($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this < $this->resolveCarbon($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function isBefore($date): bool + { + return $this->lessThan($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($date): bool + { + return $this->lessThanOrEqualTo($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThanOrEqualTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this <= $this->resolveCarbon($date); + } + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->between('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = true): bool + { + $date1 = $this->resolveCarbon($date1); + $date2 = $this->resolveCarbon($date2); + + if ($date1->greaterThan($date2)) { + [$date1, $date2] = [$date2, $date1]; + } + + if ($equal) { + return $this >= $date1 && $this <= $date2; + } + + return $this > $date1 && $this < $date2; + } + + /** + * Determines if the instance is between two others, bounds included. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenIncluded($date1, $date2): bool + { + return $this->between($date1, $date2, true); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenExcluded($date1, $date2): bool + { + return $this->between($date1, $date2, false); + } + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($date1, $date2, $equal = true): bool + { + return $this->between($date1, $date2, $equal); + } + + /** + * Determines if the instance is a weekday. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekday(); // false + * Carbon::parse('2019-07-15')->isWeekday(); // true + * ``` + * + * @return bool + */ + public function isWeekday() + { + return !$this->isWeekend(); + } + + /** + * Determines if the instance is a weekend day. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekend(); // true + * Carbon::parse('2019-07-15')->isWeekend(); // false + * ``` + * + * @return bool + */ + public function isWeekend() + { + return \in_array($this->dayOfWeek, static::$weekendDays, true); + } + + /** + * Determines if the instance is yesterday. + * + * @example + * ``` + * Carbon::yesterday()->isYesterday(); // true + * Carbon::tomorrow()->isYesterday(); // false + * ``` + * + * @return bool + */ + public function isYesterday() + { + return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is today. + * + * @example + * ``` + * Carbon::today()->isToday(); // true + * Carbon::tomorrow()->isToday(); // false + * ``` + * + * @return bool + */ + public function isToday() + { + return $this->toDateString() === $this->nowWithSameTz()->toDateString(); + } + + /** + * Determines if the instance is tomorrow. + * + * @example + * ``` + * Carbon::tomorrow()->isTomorrow(); // true + * Carbon::yesterday()->isTomorrow(); // false + * ``` + * + * @return bool + */ + public function isTomorrow() + { + return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @example + * ``` + * Carbon::now()->addHours(5)->isFuture(); // true + * Carbon::now()->subHours(5)->isFuture(); // false + * ``` + * + * @return bool + */ + public function isFuture() + { + return $this->greaterThan($this->nowWithSameTz()); + } + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @example + * ``` + * Carbon::now()->subHours(5)->isPast(); // true + * Carbon::now()->addHours(5)->isPast(); // false + * ``` + * + * @return bool + */ + public function isPast() + { + return $this->lessThan($this->nowWithSameTz()); + } + + /** + * Determines if the instance is a leap year. + * + * @example + * ``` + * Carbon::parse('2020-01-01')->isLeapYear(); // true + * Carbon::parse('2019-01-01')->isLeapYear(); // false + * ``` + * + * @return bool + */ + public function isLeapYear() + { + return $this->rawFormat('L') === '1'; + } + + /** + * Determines if the instance is a long year (using calendar year). + * + * ⚠️ This method completely ignores month and day to use the numeric year number, + * it's not correct if the exact date matters. For instance as `2019-12-30` is already + * in the first week of the 2020 year, if you want to know from this date if ISO week + * year 2020 is a long year, use `isLongIsoYear` instead. + * + * @example + * ``` + * Carbon::create(2015)->isLongYear(); // true + * Carbon::create(2016)->isLongYear(); // false + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear() + { + return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + + /** + * Determines if the instance is a long year (using ISO 8601 year). + * + * @example + * ``` + * Carbon::parse('2015-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-03')->isLongIsoYear(); // false + * Carbon::parse('2019-12-29')->isLongIsoYear(); // false + * Carbon::parse('2019-12-30')->isLongIsoYear(); // true + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongIsoYear() + { + return static::create($this->isoWeekYear, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + + /** + * Compares the formatted values of the two dates. + * + * @example + * ``` + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-12-13')); // true + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false + * ``` + * + * @param string $format date formats to compare. + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameAs($format, $date = null) + { + return $this->rawFormat($format) === $this->resolveCarbon($date)->rawFormat($format); + } + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::parse('2019-01-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // true + * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false + * ``` + * + * @param string $unit singular unit string + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * + * @throws BadComparisonUnitException + * + * @return bool + */ + public function isSameUnit($unit, $date = null) + { + $units = [ + // @call isSameUnit + 'year' => 'Y', + // @call isSameUnit + 'week' => 'o-W', + // @call isSameUnit + 'day' => 'Y-m-d', + // @call isSameUnit + 'hour' => 'Y-m-d H', + // @call isSameUnit + 'minute' => 'Y-m-d H:i', + // @call isSameUnit + 'second' => 'Y-m-d H:i:s', + // @call isSameUnit + 'micro' => 'Y-m-d H:i:s.u', + // @call isSameUnit + 'microsecond' => 'Y-m-d H:i:s.u', + ]; + + if (isset($units[$unit])) { + return $this->isSameAs($units[$unit], $date); + } + + if (isset($this->$unit)) { + return $this->resolveCarbon($date)->$unit === $this->$unit; + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new BadComparisonUnitException($unit); + } + + return false; + } + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::now()->isCurrentUnit('hour'); // true + * Carbon::now()->subHours(2)->isCurrentUnit('hour'); // false + * ``` + * + * @param string $unit The unit to test. + * + * @throws BadMethodCallException + * + * @return bool + */ + public function isCurrentUnit($unit) + { + return $this->{'isSame'.ucfirst($unit)}(); + } + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-03-01')); // true + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-04-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|string|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = true) + { + $date = $this->resolveCarbon($date); + + return $this->quarter === $date->quarter && (!$ofSameYear || $this->isSameYear($date)); + } + + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-01-01')); // true + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-02-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = true) + { + return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date); + } + + /** + * Checks if this day is a specific day of the week. + * + * @example + * ``` + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::WEDNESDAY); // true + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::FRIDAY); // false + * Carbon::parse('2019-07-17')->isDayOfWeek('Wednesday'); // true + * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false + * ``` + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek) + { + if (\is_string($dayOfWeek) && \defined($constant = static::class.'::'.strtoupper($dayOfWeek))) { + $dayOfWeek = \constant($constant); + } + + return $this->dayOfWeek === $dayOfWeek; + } + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @example + * ``` + * Carbon::now()->subYears(5)->isBirthday(); // true + * Carbon::now()->subYears(5)->subDay()->isBirthday(); // false + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-05')); // true + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false + * ``` + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null) + { + return $this->isSameAs('md', $date); + } + + /** + * Check if today is the last day of the Month + * + * @example + * ``` + * Carbon::parse('2019-02-28')->isLastOfMonth(); // true + * Carbon::parse('2019-03-28')->isLastOfMonth(); // false + * Carbon::parse('2019-03-30')->isLastOfMonth(); // false + * Carbon::parse('2019-03-31')->isLastOfMonth(); // true + * Carbon::parse('2019-04-30')->isLastOfMonth(); // true + * ``` + * + * @return bool + */ + public function isLastOfMonth() + { + return $this->day === $this->daysInMonth; + } + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:01')->isStartOfDay(); // false + * Carbon::parse('2019-02-28 00:00:00.000000')->isStartOfDay(true); // true + * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false) + { + /* @var CarbonInterface $this */ + return $checkMicroseconds + ? $this->rawFormat('H:i:s.u') === '00:00:00.000000' + : $this->rawFormat('H:i:s') === '00:00:00'; + } + + /** + * Check if the instance is end of day. + * + * @example + * ``` + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:58.999999')->isEndOfDay(); // false + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(true); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(true); // false + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false) + { + /* @var CarbonInterface $this */ + return $checkMicroseconds + ? $this->rawFormat('H:i:s.u') === '23:59:59.999999' + : $this->rawFormat('H:i:s') === '23:59:59'; + } + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false + * ``` + * + * @return bool + */ + public function isMidnight() + { + return $this->isStartOfDay(); + } + + /** + * Check if the instance is midday. + * + * @example + * ``` + * Carbon::parse('2019-02-28 11:59:59.999999')->isMidday(); // false + * Carbon::parse('2019-02-28 12:00:00')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false + * ``` + * + * @return bool + */ + public function isMidday() + { + /* @var CarbonInterface $this */ + return $this->rawFormat('G:i:s') === static::$midDayAt.':00:00'; + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format) + { + // createFromFormat() is known to handle edge cases silently. + // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format. + // To ensure we're really testing against our desired format, perform an additional regex validation. + + return self::matchFormatPattern((string) $date, preg_quote((string) $format, '/'), static::$regexFormats); + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormatWithModifiers($date, $format): bool + { + return self::matchFormatPattern((string) $date, (string) $format, array_merge(static::$regexFormats, static::$regexFormatModifiers)); + } + + /** + * Checks if the (date)time string is in a given format and valid to create a + * new instance. + * + * @example + * ``` + * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true + * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function canBeCreatedFromFormat($date, $format) + { + try { + // Try to create a DateTime object. Throws an InvalidArgumentException if the provided time string + // doesn't match the format in any way. + if (!static::rawCreateFromFormat($format, $date)) { + return false; + } + } catch (InvalidArgumentException $e) { + return false; + } + + return static::hasFormatWithModifiers($date, $format); + } + + /** + * Returns true if the current date matches the given string. + * + * @example + * ``` + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2018')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('Sunday')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('June')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:45')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:00')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12h')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3pm')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3am')); // false + * ``` + * + * @param string $tester day name, month name, hour, date, etc. as string + * + * @return bool + */ + public function is(string $tester) + { + $tester = trim($tester); + + if (preg_match('/^\d+$/', $tester)) { + return $this->year === (int) $tester; + } + + if (preg_match('/^(?:Jan|January|Feb|February|Mar|March|Apr|April|May|Jun|June|Jul|July|Aug|August|Sep|September|Oct|October|Nov|November|Dec|December)$/i', $tester)) { + return $this->isSameMonth(static::parse($tester), false); + } + + if (preg_match('/^\d{3,}-\d{1,2}$/', $tester)) { + return $this->isSameMonth(static::parse($tester)); + } + + if (preg_match('/^\d{1,2}-\d{1,2}$/', $tester)) { + return $this->isSameDay(static::parse($this->year.'-'.$tester)); + } + + $modifier = preg_replace('/(\d)h$/i', '$1:00', $tester); + + /* @var CarbonInterface $max */ + $median = static::parse('5555-06-15 12:30:30.555555')->modify($modifier); + $current = $this->avoidMutation(); + /* @var CarbonInterface $other */ + $other = $this->avoidMutation()->modify($modifier); + + if ($current->eq($other)) { + return true; + } + + if (preg_match('/\d:\d{1,2}:\d{1,2}$/', $tester)) { + return $current->startOfSecond()->eq($other); + } + + if (preg_match('/\d:\d{1,2}$/', $tester)) { + return $current->startOfMinute()->eq($other); + } + + if (preg_match('/\d(?:h|am|pm)$/', $tester)) { + return $current->startOfHour()->eq($other); + } + + if (preg_match( + '/^(?:january|february|march|april|may|june|july|august|september|october|november|december)(?:\s+\d+)?$/i', + $tester + )) { + return $current->startOfMonth()->eq($other->startOfMonth()); + } + + $units = [ + 'month' => [1, 'year'], + 'day' => [1, 'month'], + 'hour' => [0, 'day'], + 'minute' => [0, 'hour'], + 'second' => [0, 'minute'], + 'microsecond' => [0, 'second'], + ]; + + foreach ($units as $unit => [$minimum, $startUnit]) { + if ($minimum === $median->$unit) { + $current = $current->startOf($startUnit); + + break; + } + } + + return $current->eq($other); + } + + /** + * Checks if the (date)time string is in a given format with + * given list of pattern replacements. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * @param array $replacements + * + * @return bool + */ + private static function matchFormatPattern(string $date, string $format, array $replacements): bool + { + // Preg quote, but remove escaped backslashes since we'll deal with escaped characters in the format string. + $regex = str_replace('\\\\', '\\', $format); + // Replace not-escaped letters + $regex = preg_replace_callback( + '/(?startOfTime ?? false; + } + + /** + * Returns true if the date was created using CarbonImmutable::endOfTime() + * + * @return bool + */ + public function isEndOfTime(): bool + { + return $this->endOfTime ?? false; + } + + private function discourageNull($value): void + { + if ($value === null) { + @trigger_error("Since 2.61.0, it's deprecated to compare a date to null, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate null values.", \E_USER_DEPRECATED); + } + } + + private function discourageBoolean($value): void + { + if (\is_bool($value)) { + @trigger_error("Since 2.61.0, it's deprecated to compare a date to true or false, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate boolean values.", \E_USER_DEPRECATED); + } + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Converter.php b/libraries/Carbon/src/Carbon/Traits/Converter.php new file mode 100644 index 00000000000..45383116184 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Converter.php @@ -0,0 +1,639 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\Carbon; +use EDD\Vendor\Carbon\CarbonImmutable; +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\CarbonInterval; +use EDD\Vendor\Carbon\CarbonPeriod; +use EDD\Vendor\Carbon\CarbonPeriodImmutable; +use EDD\Vendor\Carbon\Exceptions\UnitException; +use Closure; +use DateTime; +use DateTimeImmutable; +use ReturnTypeWillChange; + +/** + * Trait Converter. + * + * Change date into different string formats and types and + * handle the string cast. + * + * Depends on the following methods: + * + * @method static copy() + */ +trait Converter +{ + use ToStringFormat; + + /** + * Returns the formatted date string on success or FALSE on failure. + * + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + #[ReturnTypeWillChange] + public function format($format) + { + $function = $this->localFormatFunction ?: static::$formatFunction; + + if (!$function) { + return $this->rawFormat($format); + } + + if (\is_string($function) && method_exists($this, $function)) { + $function = [$this, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + public function rawFormat($format) + { + return parent::format($format); + } + + /** + * Format the instance as a string using the set format + * + * @example + * ``` + * echo Carbon::now(); // EDD\Vendor\Carbon instances can be cast to string + * ``` + * + * @return string + */ + public function __toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + return $format instanceof Closure + ? $format($this) + : $this->rawFormat($format ?: ( + \defined('static::DEFAULT_TO_STRING_FORMAT') + ? static::DEFAULT_TO_STRING_FORMAT + : CarbonInterface::DEFAULT_TO_STRING_FORMAT + )); + } + + /** + * Format the instance as date + * + * @example + * ``` + * echo Carbon::now()->toDateString(); + * ``` + * + * @return string + */ + public function toDateString() + { + return $this->rawFormat('Y-m-d'); + } + + /** + * Format the instance as a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDateString(); + * ``` + * + * @return string + */ + public function toFormattedDateString() + { + return $this->rawFormat('M j, Y'); + } + + /** + * Format the instance with the day, and a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDayDateString(); + * ``` + * + * @return string + */ + public function toFormattedDayDateString(): string + { + return $this->rawFormat('D, M j, Y'); + } + + /** + * Format the instance as time + * + * @example + * ``` + * echo Carbon::now()->toTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second') + { + return $this->rawFormat(static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance as date and time + * + * @example + * ``` + * echo Carbon::now()->toDateTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second') + { + return $this->rawFormat('Y-m-d '.static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision) + { + switch (static::singularUnit($unitPrecision)) { + case 'minute': + return 'H:i'; + case 'second': + return 'H:i:s'; + case 'm': + case 'millisecond': + return 'H:i:s.v'; + case 'µ': + case 'microsecond': + return 'H:i:s.u'; + } + + throw new UnitException('Precision unit expected among: minute, second, millisecond and microsecond.'); + } + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * echo "\n"; + * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second') + { + return $this->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance with day, date and time + * + * @example + * ``` + * echo Carbon::now()->toDayDateTimeString(); + * ``` + * + * @return string + */ + public function toDayDateTimeString() + { + return $this->rawFormat('D, M j, Y g:i A'); + } + + /** + * Format the instance as ATOM + * + * @example + * ``` + * echo Carbon::now()->toAtomString(); + * ``` + * + * @return string + */ + public function toAtomString() + { + return $this->rawFormat(DateTime::ATOM); + } + + /** + * Format the instance as COOKIE + * + * @example + * ``` + * echo Carbon::now()->toCookieString(); + * ``` + * + * @return string + */ + public function toCookieString() + { + return $this->rawFormat(DateTime::COOKIE); + } + + /** + * Format the instance as ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601String(); + * ``` + * + * @return string + */ + public function toIso8601String() + { + return $this->toAtomString(); + } + + /** + * Format the instance as RFC822 + * + * @example + * ``` + * echo Carbon::now()->toRfc822String(); + * ``` + * + * @return string + */ + public function toRfc822String() + { + return $this->rawFormat(DateTime::RFC822); + } + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601ZuluString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toIso8601ZuluString($unitPrecision = 'second') + { + return $this->avoidMutation() + ->utc() + ->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision).'\Z'); + } + + /** + * Format the instance as RFC850 + * + * @example + * ``` + * echo Carbon::now()->toRfc850String(); + * ``` + * + * @return string + */ + public function toRfc850String() + { + return $this->rawFormat(DateTime::RFC850); + } + + /** + * Format the instance as RFC1036 + * + * @example + * ``` + * echo Carbon::now()->toRfc1036String(); + * ``` + * + * @return string + */ + public function toRfc1036String() + { + return $this->rawFormat(DateTime::RFC1036); + } + + /** + * Format the instance as RFC1123 + * + * @example + * ``` + * echo Carbon::now()->toRfc1123String(); + * ``` + * + * @return string + */ + public function toRfc1123String() + { + return $this->rawFormat(DateTime::RFC1123); + } + + /** + * Format the instance as RFC2822 + * + * @example + * ``` + * echo Carbon::now()->toRfc2822String(); + * ``` + * + * @return string + */ + public function toRfc2822String() + { + return $this->rawFormat(DateTime::RFC2822); + } + + /** + * Format the instance as RFC3339 + * + * @param bool $extended + * + * @example + * ``` + * echo Carbon::now()->toRfc3339String() . "\n"; + * echo Carbon::now()->toRfc3339String(true) . "\n"; + * ``` + * + * @return string + */ + public function toRfc3339String($extended = false) + { + $format = DateTime::RFC3339; + if ($extended) { + $format = DateTime::RFC3339_EXTENDED; + } + + return $this->rawFormat($format); + } + + /** + * Format the instance as RSS + * + * @example + * ``` + * echo Carbon::now()->toRssString(); + * ``` + * + * @return string + */ + public function toRssString() + { + return $this->rawFormat(DateTime::RSS); + } + + /** + * Format the instance as W3C + * + * @example + * ``` + * echo Carbon::now()->toW3cString(); + * ``` + * + * @return string + */ + public function toW3cString() + { + return $this->rawFormat(DateTime::W3C); + } + + /** + * Format the instance as RFC7231 + * + * @example + * ``` + * echo Carbon::now()->toRfc7231String(); + * ``` + * + * @return string + */ + public function toRfc7231String() + { + return $this->avoidMutation() + ->setTimezone('GMT') + ->rawFormat(\defined('static::RFC7231_FORMAT') ? static::RFC7231_FORMAT : CarbonInterface::RFC7231_FORMAT); + } + + /** + * Get default array representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toArray()); + * ``` + * + * @return array + */ + public function toArray() + { + return [ + 'year' => $this->year, + 'month' => $this->month, + 'day' => $this->day, + 'dayOfWeek' => $this->dayOfWeek, + 'dayOfYear' => $this->dayOfYear, + 'hour' => $this->hour, + 'minute' => $this->minute, + 'second' => $this->second, + 'micro' => $this->micro, + 'timestamp' => $this->timestamp, + 'formatted' => $this->rawFormat(\defined('static::DEFAULT_TO_STRING_FORMAT') ? static::DEFAULT_TO_STRING_FORMAT : CarbonInterface::DEFAULT_TO_STRING_FORMAT), + 'timezone' => $this->timezone, + ]; + } + + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject() + { + return (object) $this->toArray(); + } + + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString() + { + return $this->avoidMutation()->locale('en')->isoFormat('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept: + * 1977-04-22T01:00:00-05:00). + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toISOString() . "\n"; + * echo Carbon::now('America/Toronto')->toISOString(true) . "\n"; + * ``` + * + * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. + * + * @return null|string + */ + public function toISOString($keepOffset = false) + { + if (!$this->isValid()) { + return null; + } + + $yearFormat = $this->year < 0 || $this->year > 9999 ? 'YYYYYY' : 'YYYY'; + $tzFormat = $keepOffset ? 'Z' : '[Z]'; + $date = $keepOffset ? $this : $this->avoidMutation()->utc(); + + return $date->isoFormat("$yearFormat-MM-DD[T]HH:mm:ss.SSSSSS$tzFormat"); + } + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toJSON(); + * ``` + * + * @return null|string + */ + public function toJSON() + { + return $this->toISOString(); + } + + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime() + { + return new DateTime($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + /** + * Return native toDateTimeImmutable PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTimeImmutable()); + * ``` + * + * @return DateTimeImmutable + */ + public function toDateTimeImmutable() + { + return new DateTimeImmutable($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate() + { + return $this->toDateTime(); + } + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|EDD\Vendor\Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function toPeriod($end = null, $interval = null, $unit = null) + { + if ($unit) { + $interval = CarbonInterval::make("$interval ".static::pluralUnit($unit)); + } + + $period = ($this->isMutable() ? new CarbonPeriod() : new CarbonPeriodImmutable()) + ->setDateClass(static::class) + ->setStartDate($this); + + if ($interval) { + $period = $period->setDateInterval($interval); + } + + if (\is_int($end) || (\is_string($end) && ctype_digit($end))) { + $period = $period->setRecurrences($end); + } elseif ($end) { + $period = $period->setEndDate($end); + } + + return $period; + } + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|EDD\Vendor\Carbon|CarbonImmutable|null $end period end date + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function range($end = null, $interval = null, $unit = null) + { + return $this->toPeriod($end, $interval, $unit); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Creator.php b/libraries/Carbon/src/Carbon/Traits/Creator.php new file mode 100644 index 00000000000..cc0237d7364 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Creator.php @@ -0,0 +1,977 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\Carbon; +use EDD\Vendor\Carbon\CarbonImmutable; +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\Exceptions\InvalidDateException; +use EDD\Vendor\Carbon\Exceptions\InvalidFormatException; +use EDD\Vendor\Carbon\Exceptions\OutOfRangeException; +use EDD\Vendor\Carbon\Translator; +use Closure; +use DateMalformedStringException; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use Exception; +use ReturnTypeWillChange; + +/** + * Trait Creator. + * + * Static creators. + * + * Depends on the following methods: + * + * @method static EDD\Vendor\Carbon|CarbonImmutable getTestNow() + */ +trait Creator +{ + use ObjectInitialisation; + + /** + * The errors that can occur. + * + * @var array + */ + protected static $lastErrors; + + /** + * Create a new EDD\Vendor\Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param DateTimeInterface|string|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + */ + public function __construct($time = null, $tz = null) + { + if ($time instanceof DateTimeInterface) { + $time = $this->constructTimezoneFromDateTime($time, $tz)->format('Y-m-d H:i:s.u'); + } + + if (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) { + $time = static::createFromTimestampUTC($time)->format('Y-m-d\TH:i:s.uP'); + } + + // If the class has a test now set and we are trying to create a now() + // instance then override as required + $isNow = empty($time) || $time === 'now'; + + if (method_exists(static::class, 'hasTestNow') && + method_exists(static::class, 'getTestNow') && + static::hasTestNow() && + ($isNow || static::hasRelativeKeywords($time)) + ) { + static::mockConstructorParameters($time, $tz); + } + + // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 + if (!str_contains((string) .1, '.')) { + $locale = setlocale(LC_NUMERIC, '0'); // @codeCoverageIgnore + setlocale(LC_NUMERIC, 'C'); // @codeCoverageIgnore + } + + try { + parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); + } catch (Exception $exception) { + throw new InvalidFormatException($exception->getMessage(), 0, $exception); + } + + $this->constructedObjectId = spl_object_hash($this); + + if (isset($locale)) { + setlocale(LC_NUMERIC, $locale); // @codeCoverageIgnore + } + + self::setLastErrors(parent::getLastErrors()); + } + + /** + * Get timezone from a datetime instance. + * + * @param DateTimeInterface $date + * @param DateTimeZone|string|null $tz + * + * @return DateTimeInterface + */ + private function constructTimezoneFromDateTime(DateTimeInterface $date, &$tz) + { + if ($tz !== null) { + $safeTz = static::safeCreateDateTimeZone($tz); + + if ($safeTz) { + return ($date instanceof DateTimeImmutable ? $date : clone $date)->setTimezone($safeTz); + } + + return $date; + } + + $tz = $date->getTimezone(); + + return $date; + } + + /** + * Update constructedObjectId on cloned. + */ + public function __clone() + { + $this->constructedObjectId = spl_object_hash($this); + } + + /** + * Create a EDD\Vendor\Carbon instance from a DateTime one. + * + * @param DateTimeInterface $date + * + * @return static + */ + public static function instance($date) + { + if ($date instanceof static) { + return clone $date; + } + + static::expectDateTime($date); + + $instance = new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + + if ($date instanceof CarbonInterface) { + $settings = $date->getSettings(); + + if (!$date->hasLocalTranslator()) { + unset($settings['locale']); + } + + $instance->settings($settings); + } + + return $instance; + } + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function rawParse($time = null, $tz = null) + { + if ($time instanceof DateTimeInterface) { + return static::instance($time); + } + + try { + return new static($time, $tz); + } catch (Exception $exception) { + // @codeCoverageIgnoreStart + try { + $date = @static::now($tz)->change($time); + } catch (DateMalformedStringException $ignoredException) { + $date = null; + } + // @codeCoverageIgnoreEnd + + if (!$date) { + throw new InvalidFormatException("Could not parse '$time': ".$exception->getMessage(), 0, $exception); + } + + return $date; + } + } + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parse($time = null, $tz = null) + { + $function = static::$parseFunction; + + if (!$function) { + return static::rawParse($time, $tz); + } + + if (\is_string($function) && method_exists(static::class, $function)) { + $function = [static::class, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parseFromLocale($time, $locale = null, $tz = null) + { + return static::rawParse(static::translateTimeString($time, $locale, 'en'), $tz); + } + + /** + * Get a EDD\Vendor\Carbon instance for the current date and time. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null) + { + return new static(null, $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance for today. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null) + { + return static::rawParse('today', $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance for tomorrow. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null) + { + return static::rawParse('tomorrow', $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance for yesterday. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null) + { + return static::rawParse('yesterday', $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(9999, 12, 31, 23, 59, 59); + } + + /** + * Create a EDD\Vendor\Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(~PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(1, 1, 1, 0, 0, 0); + } + + private static function assertBetween($unit, $value, $min, $max) + { + if (static::isStrictModeEnabled() && ($value < $min || $value > $max)) { + throw new OutOfRangeException($unit, $min, $max, $value); + } + } + + private static function createNowInstance($tz) + { + if (!static::hasTestNow()) { + return static::now($tz); + } + + $now = static::getTestNow(); + + if ($now instanceof Closure) { + return $now(static::now($tz)); + } + + return $now->avoidMutation()->tz($tz); + } + + /** + * Create a new EDD\Vendor\Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param DateTimeInterface|int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) + { + if ((\is_string($year) && !is_numeric($year)) || $year instanceof DateTimeInterface) { + return static::parse($year, $tz ?: (\is_string($month) || $month instanceof DateTimeZone ? $month : null)); + } + + $defaults = null; + $getDefault = function ($unit) use ($tz, &$defaults) { + if ($defaults === null) { + $now = self::createNowInstance($tz); + + $defaults = array_combine([ + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ], explode('-', $now->rawFormat('Y-n-j-G-i-s.u'))); + } + + return $defaults[$unit]; + }; + + $year = $year ?? $getDefault('year'); + $month = $month ?? $getDefault('month'); + $day = $day ?? $getDefault('day'); + $hour = $hour ?? $getDefault('hour'); + $minute = $minute ?? $getDefault('minute'); + $second = (float) ($second ?? $getDefault('second')); + + self::assertBetween('month', $month, 0, 99); + self::assertBetween('day', $day, 0, 99); + self::assertBetween('hour', $hour, 0, 99); + self::assertBetween('minute', $minute, 0, 99); + self::assertBetween('second', $second, 0, 99); + + $fixYear = null; + + if ($year < 0) { + $fixYear = $year; + $year = 0; + } elseif ($year > 9999) { + $fixYear = $year - 9999; + $year = 9999; + } + + $second = ($second < 10 ? '0' : '').number_format($second, 6); + $instance = static::rawCreateFromFormat('!Y-n-j G:i:s.u', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + + if ($fixYear !== null) { + $instance = $instance->addYears($fixYear); + } + + return $instance; + } + + /** + * Create a new safe EDD\Vendor\Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidDateException + * + * @return static|false + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $fields = static::getRangesByUnit(); + + foreach ($fields as $field => $range) { + if ($$field !== null && (!\is_int($$field) || $$field < $range[0] || $$field > $range[1])) { + if (static::isStrictModeEnabled()) { + throw new InvalidDateException($field, $$field); + } + + return false; + } + } + + $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz); + + foreach (array_reverse($fields) as $field => $range) { + if ($$field !== null && (!\is_int($$field) || $$field !== $instance->$field)) { + if (static::isStrictModeEnabled()) { + throw new InvalidDateException($field, $$field); + } + + return false; + } + } + + return $instance; + } + + /** + * Create a new EDD\Vendor\Carbon instance from a specific date and time using strict validation. + * + * @see create() + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null): self + { + $initialStrictMode = static::isStrictModeEnabled(); + static::useStrictMode(true); + + try { + $date = static::create($year, $month, $day, $hour, $minute, $second, $tz); + } finally { + static::useStrictMode($initialStrictMode); + } + + return $date; + } + + /** + * Create a EDD\Vendor\Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, null, null, null, $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, 0, 0, 0, $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) + { + return static::create(null, null, null, $hour, $minute, $second, $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null) + { + return static::today($tz)->setTimeFromTimeString($time); + } + + /** + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $originalTz + * + * @return DateTimeInterface|false + */ + private static function createFromFormatAndTimezone($format, $time, $originalTz) + { + // Work-around for https://bugs.php.net/bug.php?id=75577 + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + $format = str_replace('.v', '.u', $format); + } + // @codeCoverageIgnoreEnd + + if ($originalTz === null) { + return parent::createFromFormat($format, (string) $time); + } + + $tz = \is_int($originalTz) + ? @timezone_name_from_abbr('', (int) ($originalTz * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE), 1) + : $originalTz; + + $tz = static::safeCreateDateTimeZone($tz, $originalTz); + + if ($tz === false) { + return false; + } + + return parent::createFromFormat($format, (string) $time, $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function rawCreateFromFormat($format, $time, $tz = null) + { + // Work-around for https://bugs.php.net/bug.php?id=80141 + $format = preg_replace('/(?getTimezone(); + } + + $mock = $mock->copy(); + + // Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag. + if (!preg_match("/{$nonEscaped}[!|]/", $format)) { + if (preg_match('/[HhGgisvuB]/', $format)) { + $mock = $mock->setTime(0, 0); + } + + $format = static::MOCK_DATETIME_FORMAT.' '.$format; + $time = ($mock instanceof self ? $mock->rawFormat(static::MOCK_DATETIME_FORMAT) : $mock->format(static::MOCK_DATETIME_FORMAT)).' '.$time; + } + + // Regenerate date from the modified format to base result on the mocked instance instead of now. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + } + + if ($date instanceof DateTimeInterface) { + $instance = static::instance($date); + $instance::setLastErrors($lastErrors); + + return $instance; + } + + if (static::isStrictModeEnabled()) { + throw new InvalidFormatException(implode(PHP_EOL, $lastErrors['errors'])); + } + + return false; + } + + /** + * Create a EDD\Vendor\Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + #[ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null) + { + $function = static::$createFromFormatFunction; + + if (!$function) { + return static::rawCreateFromFormat($format, $time, $tz); + } + + if (\is_string($function) && method_exists(static::class, $function)) { + $function = [static::class, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * Create a EDD\Vendor\Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) + { + $format = preg_replace_callback('/(? static::getTranslationMessageWith($translator, 'formats.LT', $locale, 'h:mm A'), + 'LTS' => static::getTranslationMessageWith($translator, 'formats.LTS', $locale, 'h:mm:ss A'), + 'L' => static::getTranslationMessageWith($translator, 'formats.L', $locale, 'MM/DD/YYYY'), + 'LL' => static::getTranslationMessageWith($translator, 'formats.LL', $locale, 'MMMM D, YYYY'), + 'LLL' => static::getTranslationMessageWith($translator, 'formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), + 'LLLL' => static::getTranslationMessageWith($translator, 'formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + ]; + } + + return $formats[$code] ?? preg_replace_callback( + '/MMMM|MM|DD|dddd/', + function ($code) { + return mb_substr($code[0], 1); + }, + $formats[strtoupper($code)] ?? '' + ); + }, $format); + + $format = preg_replace_callback('/(? 'd', + 'OM' => 'M', + 'OY' => 'Y', + 'OH' => 'G', + 'Oh' => 'g', + 'Om' => 'i', + 'Os' => 's', + 'D' => 'd', + 'DD' => 'd', + 'Do' => 'd', + 'd' => '!', + 'dd' => '!', + 'ddd' => 'D', + 'dddd' => 'D', + 'DDD' => 'z', + 'DDDD' => 'z', + 'DDDo' => 'z', + 'e' => '!', + 'E' => '!', + 'H' => 'G', + 'HH' => 'H', + 'h' => 'g', + 'hh' => 'h', + 'k' => 'G', + 'kk' => 'G', + 'hmm' => 'gi', + 'hmmss' => 'gis', + 'Hmm' => 'Gi', + 'Hmmss' => 'Gis', + 'm' => 'i', + 'mm' => 'i', + 'a' => 'a', + 'A' => 'a', + 's' => 's', + 'ss' => 's', + 'S' => '*', + 'SS' => '*', + 'SSS' => '*', + 'SSSS' => '*', + 'SSSSS' => '*', + 'SSSSSS' => 'u', + 'SSSSSSS' => 'u*', + 'SSSSSSSS' => 'u*', + 'SSSSSSSSS' => 'u*', + 'M' => 'm', + 'MM' => 'm', + 'MMM' => 'M', + 'MMMM' => 'M', + 'Mo' => 'm', + 'Q' => '!', + 'Qo' => '!', + 'G' => '!', + 'GG' => '!', + 'GGG' => '!', + 'GGGG' => '!', + 'GGGGG' => '!', + 'g' => '!', + 'gg' => '!', + 'ggg' => '!', + 'gggg' => '!', + 'ggggg' => '!', + 'W' => '!', + 'WW' => '!', + 'Wo' => '!', + 'w' => '!', + 'ww' => '!', + 'wo' => '!', + 'x' => 'U???', + 'X' => 'U', + 'Y' => 'Y', + 'YY' => 'y', + 'YYYY' => 'Y', + 'YYYYY' => 'Y', + 'YYYYYY' => 'Y', + 'z' => 'e', + 'zz' => 'e', + 'Z' => 'e', + 'ZZ' => 'e', + ]; + } + + $format = $replacements[$code] ?? '?'; + + if ($format === '!') { + throw new InvalidFormatException("Format $code not supported for creation."); + } + + return $format; + }, $format); + + return static::rawCreateFromFormat($format, $time, $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance from a specific format and a string in a given language. + * + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleFormat($format, $locale, $time, $tz = null) + { + $format = preg_replace_callback( + '/(?:\\\\[a-zA-Z]|[bfkqCEJKQRV]){2,}/', + static function (array $match) use ($locale): string { + $word = str_replace('\\', '', $match[0]); + $translatedWord = static::translateTimeString($word, $locale, 'en'); + + return $word === $translatedWord + ? $match[0] + : preg_replace('/[a-zA-Z]/', '\\\\$0', $translatedWord); + }, + $format + ); + + return static::rawCreateFromFormat($format, static::translateTimeString($time, $locale, 'en'), $tz); + } + + /** + * Create a EDD\Vendor\Carbon instance from a specific ISO format and a string in a given language. + * + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null) + { + $time = static::translateTimeString($time, $locale, 'en', CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM); + + return static::createFromIsoFormat($format, $time, $tz, $locale); + } + + /** + * Make a EDD\Vendor\Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @throws InvalidFormatException + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof DateTimeInterface) { + return static::instance($var); + } + + $date = null; + + if (\is_string($var)) { + $var = trim($var); + + if (!preg_match('/^P[\dT]/', $var) && + !preg_match('/^R\d/', $var) && + preg_match('/[a-z\d]/i', $var) + ) { + $date = static::parse($var); + } + } + + return $date; + } + + /** + * Set last errors. + * + * @param array|bool $lastErrors + * + * @return void + */ + private static function setLastErrors($lastErrors) + { + if (\is_array($lastErrors) || $lastErrors === false) { + static::$lastErrors = \is_array($lastErrors) ? $lastErrors : [ + 'warning_count' => 0, + 'warnings' => [], + 'error_count' => 0, + 'errors' => [], + ]; + } + } + + /** + * {@inheritdoc} + * + * @return array + */ + #[ReturnTypeWillChange] + public static function getLastErrors() + { + return static::$lastErrors; + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Date.php b/libraries/Carbon/src/Carbon/Traits/Date.php new file mode 100644 index 00000000000..d893f27b4d6 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Date.php @@ -0,0 +1,2747 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use BadMethodCallException; +use EDD\Vendor\Carbon\Carbon; +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\CarbonPeriod; +use EDD\Vendor\Carbon\CarbonTimeZone; +use EDD\Vendor\Carbon\Exceptions\BadComparisonUnitException; +use EDD\Vendor\Carbon\Exceptions\ImmutableException; +use EDD\Vendor\Carbon\Exceptions\InvalidTimeZoneException; +use EDD\Vendor\Carbon\Exceptions\InvalidTypeException; +use EDD\Vendor\Carbon\Exceptions\UnknownGetterException; +use EDD\Vendor\Carbon\Exceptions\UnknownMethodException; +use EDD\Vendor\Carbon\Exceptions\UnknownSetterException; +use EDD\Vendor\Carbon\Exceptions\UnknownUnitException; +use Closure; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use ReflectionException; +use ReturnTypeWillChange; +use Throwable; + +/** + * A simple API extension for DateTime. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(EDD\Vendor\Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonInterface years(int $value) Set current instance year to the given value. + * @method CarbonInterface year(int $value) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value) Set current instance year to the given value. + * @method CarbonInterface months(int $value) Set current instance month to the given value. + * @method CarbonInterface month(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each day or every X days if a factor is given. + * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each month or every X months if a factor is given. + * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each year or every X years if a factor is given. + * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or EDD\Vendor\Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * + * + */ +trait Date +{ + use Boundaries; + use Comparison; + use Converter; + use Creator; + use Difference; + use Macro; + use MagicParameter; + use Modifiers; + use Mutability; + use ObjectInitialisation; + use Options; + use Rounding; + use Serialization; + use Test; + use Timestamp; + use Units; + use Week; + + /** + * Names of days of the week. + * + * @var array + */ + protected static $days = [ + // @call isDayOfWeek + CarbonInterface::SUNDAY => 'Sunday', + // @call isDayOfWeek + CarbonInterface::MONDAY => 'Monday', + // @call isDayOfWeek + CarbonInterface::TUESDAY => 'Tuesday', + // @call isDayOfWeek + CarbonInterface::WEDNESDAY => 'Wednesday', + // @call isDayOfWeek + CarbonInterface::THURSDAY => 'Thursday', + // @call isDayOfWeek + CarbonInterface::FRIDAY => 'Friday', + // @call isDayOfWeek + CarbonInterface::SATURDAY => 'Saturday', + ]; + + /** + * Will UTF8 encoding be used to print localized date/time ? + * + * @var bool + */ + protected static $utf8 = false; + + /** + * List of unit and magic methods associated as doc-comments. + * + * @var array + */ + protected static $units = [ + // @call setUnit + // @call addUnit + 'year', + // @call setUnit + // @call addUnit + 'month', + // @call setUnit + // @call addUnit + 'day', + // @call setUnit + // @call addUnit + 'hour', + // @call setUnit + // @call addUnit + 'minute', + // @call setUnit + // @call addUnit + 'second', + // @call setUnit + // @call addUnit + 'milli', + // @call setUnit + // @call addUnit + 'millisecond', + // @call setUnit + // @call addUnit + 'micro', + // @call setUnit + // @call addUnit + 'microsecond', + ]; + + /** + * Creates a DateTimeZone from a string, DateTimeZone or integer offset. + * + * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * + * @throws InvalidTimeZoneException + * + * @return CarbonTimeZone|false + */ + protected static function safeCreateDateTimeZone($object, $objectDump = null) + { + return CarbonTimeZone::instance($object, $objectDump); + } + + /** + * Get the TimeZone associated with the EDD\Vendor\Carbon instance (as CarbonTimeZone). + * + * @return CarbonTimeZone + * + * @link https://php.net/manual/en/datetime.gettimezone.php + */ + #[ReturnTypeWillChange] + public function getTimezone() + { + return CarbonTimeZone::instance(parent::getTimezone()); + } + + /** + * List of minimum and maximums for each unit. + * + * @param int $daysInMonth + * + * @return array + */ + protected static function getRangesByUnit(int $daysInMonth = 31): array + { + return [ + // @call roundUnit + 'year' => [1, 9999], + // @call roundUnit + 'month' => [1, static::MONTHS_PER_YEAR], + // @call roundUnit + 'day' => [1, $daysInMonth], + // @call roundUnit + 'hour' => [0, static::HOURS_PER_DAY - 1], + // @call roundUnit + 'minute' => [0, static::MINUTES_PER_HOUR - 1], + // @call roundUnit + 'second' => [0, static::SECONDS_PER_MINUTE - 1], + ]; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Clone the current instance if it's mutable. + * + * This method is convenient to ensure you don't mutate the initial object + * but avoid to make a useless copy of it if it's already immutable. + * + * @return static + */ + public function avoidMutation(): self + { + if ($this instanceof DateTimeImmutable) { + return $this; + } + + return clone $this; + } + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz() + { + return static::now($this->getTimezone()); + } + + /** + * Throws an exception if the given object is not a DateTime and does not implement DateTimeInterface. + * + * @param mixed $date + * @param string|array $other + * + * @throws InvalidTypeException + */ + protected static function expectDateTime($date, $other = []) + { + $message = 'Expected '; + foreach ((array) $other as $expect) { + $message .= "$expect, "; + } + + if (!$date instanceof DateTime && !$date instanceof DateTimeInterface) { + throw new InvalidTypeException( + $message.'DateTime or DateTimeInterface, '. + (\is_object($date) ? \get_class($date) : \gettype($date)).' given' + ); + } + } + + /** + * Return the EDD\Vendor\Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param EDD\Vendor\Carbon|DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveCarbon($date = null) + { + if (!$date) { + return $this->nowWithSameTz(); + } + + if (\is_string($date)) { + return static::parse($date, $this->getTimezone()); + } + + static::expectDateTime($date, ['null', 'string']); + + return $date instanceof self ? $date : static::instance($date); + } + + /** + * Return the EDD\Vendor\Carbon instance passed through, a now instance in UTC + * if null given or parse the input if string given (using current timezone + * then switching to UTC). + * + * @param EDD\Vendor\Carbon|DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveUTC($date = null): self + { + if (!$date) { + return static::now('UTC'); + } + + if (\is_string($date)) { + return static::parse($date, $this->getTimezone())->utc(); + } + + static::expectDateTime($date, ['null', 'string']); + + return $date instanceof self ? $date : static::instance($date)->utc(); + } + + /** + * Return the EDD\Vendor\Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param EDD\Vendor\Carbon|\EDD\Vendor\Carbon\CarbonPeriod|\EDD\Vendor\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * + * @return static + */ + public function carbonize($date = null) + { + if ($date instanceof DateInterval) { + return $this->avoidMutation()->add($date); + } + + if ($date instanceof DatePeriod || $date instanceof CarbonPeriod) { + $date = $date->getStartDate(); + } + + return $this->resolveCarbon($date); + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the EDD\Vendor\Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Get a part of the EDD\Vendor\Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function get($name) + { + static $formats = [ + // @property int + 'year' => 'Y', + // @property int + 'yearIso' => 'o', + // @property int + // @call isSameUnit + 'month' => 'n', + // @property int + 'day' => 'j', + // @property int + 'hour' => 'G', + // @property int + 'minute' => 'i', + // @property int + 'second' => 's', + // @property int + 'micro' => 'u', + // @property int + 'microsecond' => 'u', + // @property-read int 0 (for Sunday) through 6 (for Saturday) + 'dayOfWeek' => 'w', + // @property-read int 1 (for Monday) through 7 (for Sunday) + 'dayOfWeekIso' => 'N', + // @property-read int ISO-8601 week number of year, weeks starting on Monday + 'weekOfYear' => 'W', + // @property-read int number of days in the given month + 'daysInMonth' => 't', + // @property int|float|string seconds since the Unix Epoch + 'timestamp' => 'U', + // @property-read string "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + 'latinMeridiem' => 'a', + // @property-read string "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + 'latinUpperMeridiem' => 'A', + // @property string the day of week in English + 'englishDayOfWeek' => 'l', + // @property string the abbreviated day of week in English + 'shortEnglishDayOfWeek' => 'D', + // @property string the month in English + 'englishMonth' => 'F', + // @property string the abbreviated month in English + 'shortEnglishMonth' => 'M', + // @property string the day of week in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('MMM') instead. + // since: 2.55.0 + 'localeDayOfWeek' => '%A', + // @property string the abbreviated day of week in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('dddd') instead. + // since: 2.55.0 + 'shortLocaleDayOfWeek' => '%a', + // @property string the month in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('ddd') instead. + // since: 2.55.0 + 'localeMonth' => '%B', + // @property string the abbreviated month in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('MMMM') instead. + // since: 2.55.0 + 'shortLocaleMonth' => '%b', + // @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + 'timezoneAbbreviatedName' => 'T', + // @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + 'tzAbbrName' => 'T', + ]; + + switch (true) { + case isset($formats[$name]): + $format = $formats[$name]; + $method = str_starts_with($format, '%') ? 'formatLocalized' : 'rawFormat'; + $value = $this->$method($format); + + return is_numeric($value) ? (int) $value : $value; + + // @property-read string long name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + case $name === 'dayName': + return $this->getTranslatedDayName(); + // @property-read string short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + case $name === 'shortDayName': + return $this->getTranslatedShortDayName(); + // @property-read string very short name of weekday translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + case $name === 'minDayName': + return $this->getTranslatedMinDayName(); + // @property-read string long name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + case $name === 'monthName': + return $this->getTranslatedMonthName(); + // @property-read string short name of month translated according to EDD\Vendor\Carbon locale, in english if no translation available for current language + case $name === 'shortMonthName': + return $this->getTranslatedShortMonthName(); + // @property-read string lowercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + case $name === 'meridiem': + return $this->meridiem(true); + // @property-read string uppercase meridiem mark translated according to EDD\Vendor\Carbon locale, in latin if no translation available for current language + case $name === 'upperMeridiem': + return $this->meridiem(); + // @property-read int current hour from 1 to 24 + case $name === 'noZeroHour': + return $this->hour ?: 24; + // @property int + case $name === 'milliseconds': + // @property int + case $name === 'millisecond': + // @property int + case $name === 'milli': + return (int) floor(((int) $this->rawFormat('u')) / 1000); + + // @property int 1 through 53 + case $name === 'week': + return (int) $this->week(); + + // @property int 1 through 53 + case $name === 'isoWeek': + return (int) $this->isoWeek(); + + // @property int year according to week format + case $name === 'weekYear': + return (int) $this->weekYear(); + + // @property int year according to ISO week format + case $name === 'isoWeekYear': + return (int) $this->isoWeekYear(); + + // @property-read int 51 through 53 + case $name === 'weeksInYear': + return $this->weeksInYear(); + + // @property-read int 51 through 53 + case $name === 'isoWeeksInYear': + return $this->isoWeeksInYear(); + + // @property-read int 1 through 5 + case $name === 'weekOfMonth': + return (int) ceil($this->day / static::DAYS_PER_WEEK); + + // @property-read int 1 through 5 + case $name === 'weekNumberInMonth': + return (int) ceil(($this->day + $this->avoidMutation()->startOfMonth()->dayOfWeekIso - 1) / static::DAYS_PER_WEEK); + + // @property-read int 0 through 6 + case $name === 'firstWeekDay': + return $this->localTranslator ? ($this->getTranslationMessage('first_day_of_week') ?? 0) : static::getWeekStartsAt(); + + // @property-read int 0 through 6 + case $name === 'lastWeekDay': + return $this->localTranslator ? (($this->getTranslationMessage('first_day_of_week') ?? 0) + static::DAYS_PER_WEEK - 1) % static::DAYS_PER_WEEK : static::getWeekEndsAt(); + + // @property int 1 through 366 + case $name === 'dayOfYear': + return 1 + (int) ($this->rawFormat('z')); + + // @property-read int 365 or 366 + case $name === 'daysInYear': + return $this->isLeapYear() ? 366 : 365; + + // @property int does a diffInYears() with default parameters + case $name === 'age': + return $this->diffInYears(); + + // @property-read int the quarter of this instance, 1 - 4 + // @call isSameUnit + case $name === 'quarter': + return (int) ceil($this->month / static::MONTHS_PER_QUARTER); + + // @property-read int the decade of this instance + // @call isSameUnit + case $name === 'decade': + return (int) ceil($this->year / static::YEARS_PER_DECADE); + + // @property-read int the century of this instance + // @call isSameUnit + case $name === 'century': + $factor = 1; + $year = $this->year; + if ($year < 0) { + $year = -$year; + $factor = -1; + } + + return (int) ($factor * ceil($year / static::YEARS_PER_CENTURY)); + + // @property-read int the millennium of this instance + // @call isSameUnit + case $name === 'millennium': + $factor = 1; + $year = $this->year; + if ($year < 0) { + $year = -$year; + $factor = -1; + } + + return (int) ($factor * ceil($year / static::YEARS_PER_MILLENNIUM)); + + // @property int the timezone offset in seconds from UTC + case $name === 'offset': + return $this->getOffset(); + + // @property int the timezone offset in minutes from UTC + case $name === 'offsetMinutes': + return $this->getOffset() / static::SECONDS_PER_MINUTE; + + // @property int the timezone offset in hours from UTC + case $name === 'offsetHours': + return $this->getOffset() / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR; + + // @property-read bool daylight savings time indicator, true if DST, false otherwise + case $name === 'dst': + return $this->rawFormat('I') === '1'; + + // @property-read bool checks if the timezone is local, true if local, false otherwise + case $name === 'local': + return $this->getOffset() === $this->avoidMutation()->setTimezone(date_default_timezone_get())->getOffset(); + + // @property-read bool checks if the timezone is UTC, true if UTC, false otherwise + case $name === 'utc': + return $this->getOffset() === 0; + + // @property CarbonTimeZone $timezone the current timezone + // @property CarbonTimeZone $tz alias of $timezone + case $name === 'timezone' || $name === 'tz': + return CarbonTimeZone::instance($this->getTimezone()); + + // @property-read string $timezoneName the current timezone name + // @property-read string $tzName alias of $timezoneName + case $name === 'timezoneName' || $name === 'tzName': + return $this->getTimezone()->getName(); + + // @property-read string locale of the current instance + case $name === 'locale': + return $this->getTranslatorLocale(); + + default: + $macro = $this->getLocalMacro('get'.ucfirst($name)); + + if ($macro) { + return $this->executeCallableWithContext($macro); + } + + throw new UnknownGetterException($name); + } + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + try { + $this->__get($name); + } catch (UnknownGetterException | ReflectionException $e) { + return false; + } + + return true; + } + + /** + * Set a part of the EDD\Vendor\Carbon object + * + * @param string $name + * @param string|int|DateTimeZone $value + * + * @throws UnknownSetterException|ReflectionException + * + * @return void + */ + public function __set($name, $value) + { + if ($this->constructedObjectId === spl_object_hash($this)) { + $this->set($name, $value); + + return; + } + + $this->$name = $value; + } + + /** + * Set a part of the EDD\Vendor\Carbon object + * + * @param string|array $name + * @param string|int|DateTimeZone $value + * + * @throws ImmutableException|UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null) + { + if ($this->isImmutable()) { + throw new ImmutableException(sprintf('%s class', static::class)); + } + + if (\is_array($name)) { + foreach ($name as $key => $value) { + $this->set($key, $value); + } + + return $this; + } + + switch ($name) { + case 'milliseconds': + case 'millisecond': + case 'milli': + case 'microseconds': + case 'microsecond': + case 'micro': + if (str_starts_with($name, 'milli')) { + $value *= 1000; + } + + while ($value < 0) { + $this->subSecond(); + $value += static::MICROSECONDS_PER_SECOND; + } + + while ($value >= static::MICROSECONDS_PER_SECOND) { + $this->addSecond(); + $value -= static::MICROSECONDS_PER_SECOND; + } + + $this->modify($this->rawFormat('H:i:s.').str_pad((string) round($value), 6, '0', STR_PAD_LEFT)); + + break; + + case 'year': + case 'month': + case 'day': + case 'hour': + case 'minute': + case 'second': + [$year, $month, $day, $hour, $minute, $second] = array_map('intval', explode('-', $this->rawFormat('Y-n-j-G-i-s'))); + $$name = $value; + $this->setDateTime($year, $month, $day, $hour, $minute, $second); + + break; + + case 'week': + $this->week($value); + + break; + + case 'isoWeek': + $this->isoWeek($value); + + break; + + case 'weekYear': + $this->weekYear($value); + + break; + + case 'isoWeekYear': + $this->isoWeekYear($value); + + break; + + case 'dayOfYear': + $this->addDays($value - $this->dayOfYear); + + break; + + case 'timestamp': + $this->setTimestamp($value); + + break; + + case 'offset': + $this->setTimezone(static::safeCreateDateTimeZone($value / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR)); + + break; + + case 'offsetMinutes': + $this->setTimezone(static::safeCreateDateTimeZone($value / static::MINUTES_PER_HOUR)); + + break; + + case 'offsetHours': + $this->setTimezone(static::safeCreateDateTimeZone($value)); + + break; + + case 'timezone': + case 'tz': + $this->setTimezone($value); + + break; + + default: + $macro = $this->getLocalMacro('set'.ucfirst($name)); + + if ($macro) { + $this->executeCallableWithContext($macro, $value); + + break; + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnknownSetterException($name); + } + + $this->$name = $value; + } + + return $this; + } + + protected function getTranslatedFormByRegExp($baseKey, $keySuffix, $context, $subKey, $defaultValue) + { + $key = $baseKey.$keySuffix; + $standaloneKey = "{$key}_standalone"; + $baseTranslation = $this->getTranslationMessage($key); + + if ($baseTranslation instanceof Closure) { + return $baseTranslation($this, $context, $subKey) ?: $defaultValue; + } + + if ( + $this->getTranslationMessage("$standaloneKey.$subKey") && + (!$context || (($regExp = $this->getTranslationMessage("{$baseKey}_regexp")) && !preg_match($regExp, $context))) + ) { + $key = $standaloneKey; + } + + return $this->getTranslationMessage("$key.$subKey", null, $defaultValue); + } + + /** + * Get the translation of the current week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "", "_short" or "_min" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null) + { + return $this->getTranslatedFormByRegExp('weekdays', $keySuffix, $context, $this->dayOfWeek, $defaultValue ?: $this->englishDayOfWeek); + } + + /** + * Get the translation of the current short week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortDayName($context = null) + { + return $this->getTranslatedDayName($context, '_short', $this->shortEnglishDayOfWeek); + } + + /** + * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedMinDayName($context = null) + { + return $this->getTranslatedDayName($context, '_min', $this->shortEnglishDayOfWeek); + } + + /** + * Get the translation of the current month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "" or "_short" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null) + { + return $this->getTranslatedFormByRegExp('months', $keySuffix, $context, $this->month - 1, $defaultValue ?: $this->englishMonth); + } + + /** + * Get the translation of the current short month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortMonthName($context = null) + { + return $this->getTranslatedMonthName($context, '_short', $this->shortEnglishMonth); + } + + /** + * Get/set the day of year. + * + * @param int|null $value new value for day of year if using as setter. + * + * @return static|int + */ + public function dayOfYear($value = null) + { + $dayOfYear = $this->dayOfYear; + + return $value === null ? $dayOfYear : $this->addDays($value - $dayOfYear); + } + + /** + * Get/set the weekday from 0 (Sunday) to 6 (Saturday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function weekday($value = null) + { + if ($value === null) { + return $this->dayOfWeek; + } + + $firstDay = (int) ($this->getTranslationMessage('first_day_of_week') ?? 0); + $dayOfWeek = ($this->dayOfWeek + 7 - $firstDay) % 7; + + return $this->addDays((($value + 7 - $firstDay) % 7) - $dayOfWeek); + } + + /** + * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function isoWeekday($value = null) + { + $dayOfWeekIso = $this->dayOfWeekIso; + + return $value === null ? $dayOfWeekIso : $this->addDays($value - $dayOfWeekIso); + } + + /** + * Return the number of days since the start of the week (using the current locale or the first parameter + * if explicitly given). + * + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return int + */ + public function getDaysFromStartOfWeek(int $weekStartsAt = null): int + { + $firstDay = (int) ($weekStartsAt ?? $this->getTranslationMessage('first_day_of_week') ?? 0); + + return ($this->dayOfWeek + 7 - $firstDay) % 7; + } + + /** + * Set the day (keeping the current time) to the start of the week + the number of days passed as the first + * parameter. First day of week is driven by the locale unless explicitly set with the second parameter. + * + * @param int $numberOfDays number of days to add after the start of the current week + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return static + */ + public function setDaysFromStartOfWeek(int $numberOfDays, int $weekStartsAt = null) + { + return $this->addDays($numberOfDays - $this->getDaysFromStartOfWeek($weekStartsAt)); + } + + /** + * Set any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function setUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + try { + $original = $this->avoidMutation(); + /** @var static $date */ + $date = $this->$valueUnit($value); + $end = $original->avoidMutation()->endOf($overflowUnit); + $start = $original->avoidMutation()->startOf($overflowUnit); + if ($date < $start) { + $date = $date->setDateTimeFrom($start); + } elseif ($date > $end) { + $date = $date->setDateTimeFrom($end); + } + + return $date; + } catch (BadMethodCallException | ReflectionException $exception) { + throw new UnknownUnitException($valueUnit, 0, $exception); + } + } + + /** + * Add any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function addUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit + $value, $overflowUnit); + } + + /** + * Subtract any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function subUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit - $value, $overflowUnit); + } + + /** + * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. + * + * @param int|null $minuteOffset + * + * @return int|static + */ + public function utcOffset(int $minuteOffset = null) + { + if (\func_num_args() < 1) { + return $this->offsetMinutes; + } + + return $this->setTimezone(CarbonTimeZone::createFromMinuteOffset($minuteOffset)); + } + + /** + * Set the date with gregorian year, month and day numbers. + * + * @see https://php.net/manual/en/datetime.setdate.php + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setDate($year, $month, $day) + { + return parent::setDate((int) $year, (int) $month, (int) $day); + } + + /** + * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. + * + * @see https://php.net/manual/en/datetime.setisodate.php + * + * @param int $year + * @param int $week + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setISODate($year, $week, $day = 1) + { + return parent::setISODate((int) $year, (int) $week, (int) $day); + } + + /** + * Set the date and time all together. + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0) + { + return $this->setDate($year, $month, $day)->setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + } + + /** + * Resets the current time of the DateTime object to a different time. + * + * @see https://php.net/manual/en/datetime.settime.php + * + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTime($hour, $minute, $second = 0, $microseconds = 0) + { + return parent::setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + } + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimestamp($unixTimestamp) + { + [$timestamp, $microseconds] = self::getIntegerAndDecimalParts($unixTimestamp); + + return parent::setTimestamp((int) $timestamp)->setMicroseconds((int) $microseconds); + } + + /** + * Set the time by time string. + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time) + { + if (!str_contains($time, ':')) { + $time .= ':0'; + } + + return $this->modify($time); + } + + /** + * @alias setTimezone + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function timezone($value) + { + return $this->setTimezone($value); + } + + /** + * Set the timezone or returns the timezone name if no arguments passed. + * + * @param DateTimeZone|string $value + * + * @return static|string + */ + public function tz($value = null) + { + if (\func_num_args() < 1) { + return $this->tzName; + } + + return $this->setTimezone($value); + } + + /** + * Set the instance's timezone from a string or object. + * + * @param DateTimeZone|string $value + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimezone($value) + { + $tz = static::safeCreateDateTimeZone($value); + + if ($tz === false && !self::isStrictModeEnabled()) { + $tz = new CarbonTimeZone(); + } + + return parent::setTimezone($tz); + } + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function shiftTimezone($value) + { + $dateTimeString = $this->format('Y-m-d H:i:s.u'); + + return $this + ->setTimezone($value) + ->modify($dateTimeString); + } + + /** + * Set the instance's timezone to UTC. + * + * @return static + */ + public function utc() + { + return $this->setTimezone('UTC'); + } + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param EDD\Vendor\Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setDateFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->setDate($date->year, $date->month, $date->day); + } + + /** + * Set the hour, minute, second and microseconds for this instance to that of the passed instance. + * + * @param EDD\Vendor\Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setTimeFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->setTime($date->hour, $date->minute, $date->second, $date->microsecond); + } + + /** + * Set the date and time for this instance to that of the passed instance. + * + * @param EDD\Vendor\Carbon|DateTimeInterface $date + * + * @return static + */ + public function setDateTimeFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->modify($date->rawFormat('Y-m-d H:i:s.u')); + } + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays() + { + return static::$days; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// WEEK SPECIAL DAYS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + private static function getFirstDayOfWeek(): int + { + return (int) static::getTranslationMessageWith( + static::getTranslator(), + 'first_day_of_week' + ); + } + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt() + { + if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { + return self::getFirstDayOfWeek(); + } + + return static::$weekStartsAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * + * Set the first day of week + * + * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekStartsAt($day) + { + static::$weekStartsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + } + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt() + { + if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { + return (int) (static::DAYS_PER_WEEK - 1 + self::getFirstDayOfWeek()) % static::DAYS_PER_WEEK; + } + + return static::$weekEndsAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * + * Set the last day of week + * + * @param int|string $day week end day (or 'auto' to get the day before the first day of week + * from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekEndsAt($day) + { + static::$weekEndsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + } + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays() + { + return static::$weekendDays; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days) + { + static::$weekendDays = $days; + } + + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time) + { + if (!$time || strtotime($time) === false) { + return false; + } + + $date1 = new DateTime('2000-01-01T00:00:00Z'); + $date1->modify($time); + $date2 = new DateTime('2001-12-25T00:00:00Z'); + $date2->modify($time); + + return $date1 != $date2; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// STRING FORMATTING ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * + * Set if UTF8 will be used for localized date/time. + * + * @param bool $utf8 + */ + public static function setUtf8($utf8) + { + static::$utf8 = $utf8; + } + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() https://php.net/setlocale. + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat() instead. + * Deprecated since 2.55.0 + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format) + { + // Check for Windows to find and replace the %e modifier correctly. + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $format = preg_replace('#(?toDateTimeString()); + $formatted = ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) + ? strftime($format, $time) + : @strftime($format, $time); + + return static::$utf8 + ? ( + \function_exists('mb_convert_encoding') + ? mb_convert_encoding($formatted, 'UTF-8', mb_list_encodings()) + : utf8_encode($formatted) + ) + : $formatted; + } + + /** + * Returns list of locale formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getIsoFormats($locale = null) + { + return [ + 'LT' => $this->getTranslationMessage('formats.LT', $locale, 'h:mm A'), + 'LTS' => $this->getTranslationMessage('formats.LTS', $locale, 'h:mm:ss A'), + 'L' => $this->getTranslationMessage('formats.L', $locale, 'MM/DD/YYYY'), + 'LL' => $this->getTranslationMessage('formats.LL', $locale, 'MMMM D, YYYY'), + 'LLL' => $this->getTranslationMessage('formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), + 'LLLL' => $this->getTranslationMessage('formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + 'l' => $this->getTranslationMessage('formats.l', $locale), + 'll' => $this->getTranslationMessage('formats.ll', $locale), + 'lll' => $this->getTranslationMessage('formats.lll', $locale), + 'llll' => $this->getTranslationMessage('formats.llll', $locale), + ]; + } + + /** + * Returns list of calendar formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getCalendarFormats($locale = null) + { + return [ + 'sameDay' => $this->getTranslationMessage('calendar.sameDay', $locale, '[Today at] LT'), + 'nextDay' => $this->getTranslationMessage('calendar.nextDay', $locale, '[Tomorrow at] LT'), + 'nextWeek' => $this->getTranslationMessage('calendar.nextWeek', $locale, 'dddd [at] LT'), + 'lastDay' => $this->getTranslationMessage('calendar.lastDay', $locale, '[Yesterday at] LT'), + 'lastWeek' => $this->getTranslationMessage('calendar.lastWeek', $locale, '[Last] dddd [at] LT'), + 'sameElse' => $this->getTranslationMessage('calendar.sameElse', $locale, 'L'), + ]; + } + + /** + * Returns list of locale units for ISO formatting. + * + * @return array + */ + public static function getIsoUnits() + { + static $units = null; + + if ($units === null) { + $units = [ + 'OD' => ['getAltNumber', ['day']], + 'OM' => ['getAltNumber', ['month']], + 'OY' => ['getAltNumber', ['year']], + 'OH' => ['getAltNumber', ['hour']], + 'Oh' => ['getAltNumber', ['h']], + 'Om' => ['getAltNumber', ['minute']], + 'Os' => ['getAltNumber', ['second']], + 'D' => 'day', + 'DD' => ['rawFormat', ['d']], + 'Do' => ['ordinal', ['day', 'D']], + 'd' => 'dayOfWeek', + 'dd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedMinDayName($originalFormat); + }, + 'ddd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedShortDayName($originalFormat); + }, + 'dddd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedDayName($originalFormat); + }, + 'DDD' => 'dayOfYear', + 'DDDD' => ['getPaddedUnit', ['dayOfYear', 3]], + 'DDDo' => ['ordinal', ['dayOfYear', 'DDD']], + 'e' => ['weekday', []], + 'E' => 'dayOfWeekIso', + 'H' => ['rawFormat', ['G']], + 'HH' => ['rawFormat', ['H']], + 'h' => ['rawFormat', ['g']], + 'hh' => ['rawFormat', ['h']], + 'k' => 'noZeroHour', + 'kk' => ['getPaddedUnit', ['noZeroHour']], + 'hmm' => ['rawFormat', ['gi']], + 'hmmss' => ['rawFormat', ['gis']], + 'Hmm' => ['rawFormat', ['Gi']], + 'Hmmss' => ['rawFormat', ['Gis']], + 'm' => 'minute', + 'mm' => ['rawFormat', ['i']], + 'a' => 'meridiem', + 'A' => 'upperMeridiem', + 's' => 'second', + 'ss' => ['getPaddedUnit', ['second']], + 'S' => function (CarbonInterface $date) { + return (string) floor($date->micro / 100000); + }, + 'SS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 10000), 2, '0', STR_PAD_LEFT); + }, + 'SSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 1000), 3, '0', STR_PAD_LEFT); + }, + 'SSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 100), 4, '0', STR_PAD_LEFT); + }, + 'SSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 10), 5, '0', STR_PAD_LEFT); + }, + 'SSSSSS' => ['getPaddedUnit', ['micro', 6]], + 'SSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 10), 7, '0', STR_PAD_LEFT); + }, + 'SSSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 100), 8, '0', STR_PAD_LEFT); + }, + 'SSSSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 1000), 9, '0', STR_PAD_LEFT); + }, + 'M' => 'month', + 'MM' => ['rawFormat', ['m']], + 'MMM' => function (CarbonInterface $date, $originalFormat = null) { + $month = $date->getTranslatedShortMonthName($originalFormat); + $suffix = $date->getTranslationMessage('mmm_suffix'); + if ($suffix && $month !== $date->monthName) { + $month .= $suffix; + } + + return $month; + }, + 'MMMM' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedMonthName($originalFormat); + }, + 'Mo' => ['ordinal', ['month', 'M']], + 'Q' => 'quarter', + 'Qo' => ['ordinal', ['quarter', 'M']], + 'G' => 'isoWeekYear', + 'GG' => ['getPaddedUnit', ['isoWeekYear']], + 'GGG' => ['getPaddedUnit', ['isoWeekYear', 3]], + 'GGGG' => ['getPaddedUnit', ['isoWeekYear', 4]], + 'GGGGG' => ['getPaddedUnit', ['isoWeekYear', 5]], + 'g' => 'weekYear', + 'gg' => ['getPaddedUnit', ['weekYear']], + 'ggg' => ['getPaddedUnit', ['weekYear', 3]], + 'gggg' => ['getPaddedUnit', ['weekYear', 4]], + 'ggggg' => ['getPaddedUnit', ['weekYear', 5]], + 'W' => 'isoWeek', + 'WW' => ['getPaddedUnit', ['isoWeek']], + 'Wo' => ['ordinal', ['isoWeek', 'W']], + 'w' => 'week', + 'ww' => ['getPaddedUnit', ['week']], + 'wo' => ['ordinal', ['week', 'w']], + 'x' => ['valueOf', []], + 'X' => 'timestamp', + 'Y' => 'year', + 'YY' => ['rawFormat', ['y']], + 'YYYY' => ['getPaddedUnit', ['year', 4]], + 'YYYYY' => ['getPaddedUnit', ['year', 5]], + 'YYYYYY' => function (CarbonInterface $date) { + return ($date->year < 0 ? '' : '+').$date->getPaddedUnit('year', 6); + }, + 'z' => ['rawFormat', ['T']], + 'zz' => 'tzName', + 'Z' => ['getOffsetString', []], + 'ZZ' => ['getOffsetString', ['']], + ]; + } + + return $units; + } + + /** + * Returns a unit of the instance padded with 0 by default or any other string if specified. + * + * @param string $unit EDD\Vendor\Carbon unit name + * @param int $length Length of the output (2 by default) + * @param string $padString String to use for padding ("0" by default) + * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) + * + * @return string + */ + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = STR_PAD_LEFT) + { + return ($this->$unit < 0 ? '-' : '').str_pad((string) abs($this->$unit), $length, $padString, $padType); + } + + /** + * Return a property with its ordinal. + * + * @param string $key + * @param string|null $period + * + * @return string + */ + public function ordinal(string $key, ?string $period = null): string + { + $number = $this->$key; + $result = $this->translate('ordinal', [ + ':number' => $number, + ':period' => (string) $period, + ]); + + return (string) ($result === 'ordinal' ? $number : $result); + } + + /** + * Return the meridiem of the current time in the current locale. + * + * @param bool $isLower if true, returns lowercase variant if available in the current locale. + * + * @return string + */ + public function meridiem(bool $isLower = false): string + { + $hour = $this->hour; + $index = $hour < 12 ? 0 : 1; + + if ($isLower) { + $key = 'meridiem.'.($index + 2); + $result = $this->translate($key); + + if ($result !== $key) { + return $result; + } + } + + $key = "meridiem.$index"; + $result = $this->translate($key); + if ($result === $key) { + $result = $this->translate('meridiem', [ + ':hour' => $this->hour, + ':minute' => $this->minute, + ':isLower' => $isLower, + ]); + + if ($result === 'meridiem') { + return $isLower ? $this->latinMeridiem : $this->latinUpperMeridiem; + } + } elseif ($isLower) { + $result = mb_strtolower($result); + } + + return $result; + } + + /** + * Returns the alternative number for a given date property if available in the current locale. + * + * @param string $key date property + * + * @return string + */ + public function getAltNumber(string $key): string + { + return $this->translateNumber(\strlen($key) > 1 ? $this->$key : $this->rawFormat('h')); + } + + /** + * Format in the current language using ISO replacement patterns. + * + * @param string $format + * @param string|null $originalFormat provide context if a chunk has been passed alone + * + * @return string + */ + public function isoFormat(string $format, ?string $originalFormat = null): string + { + $result = ''; + $length = mb_strlen($format); + $originalFormat = $originalFormat ?: $format; + $inEscaped = false; + $formats = null; + $units = null; + + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($format, $i, 1); + + if ($char === '\\') { + $result .= mb_substr($format, ++$i, 1); + + continue; + } + + if ($char === '[' && !$inEscaped) { + $inEscaped = true; + + continue; + } + + if ($char === ']' && $inEscaped) { + $inEscaped = false; + + continue; + } + + if ($inEscaped) { + $result .= $char; + + continue; + } + + $input = mb_substr($format, $i); + + if (preg_match('/^(LTS|LT|l{1,4}|L{1,4})/', $input, $match)) { + if ($formats === null) { + $formats = $this->getIsoFormats(); + } + + $code = $match[0]; + $sequence = $formats[$code] ?? preg_replace_callback( + '/MMMM|MM|DD|dddd/', + function ($code) { + return mb_substr($code[0], 1); + }, + $formats[strtoupper($code)] ?? '' + ); + $rest = mb_substr($format, $i + mb_strlen($code)); + $format = mb_substr($format, 0, $i).$sequence.$rest; + $length = mb_strlen($format); + $input = $sequence.$rest; + } + + if (preg_match('/^'.CarbonInterface::ISO_FORMAT_REGEXP.'/', $input, $match)) { + $code = $match[0]; + + if ($units === null) { + $units = static::getIsoUnits(); + } + + $sequence = $units[$code] ?? ''; + + if ($sequence instanceof Closure) { + $sequence = $sequence($this, $originalFormat); + } elseif (\is_array($sequence)) { + try { + $sequence = $this->{$sequence[0]}(...$sequence[1]); + } catch (ReflectionException | InvalidArgumentException | BadMethodCallException $e) { + $sequence = ''; + } + } elseif (\is_string($sequence)) { + $sequence = $this->$sequence ?? $code; + } + + $format = mb_substr($format, 0, $i).$sequence.mb_substr($format, $i + mb_strlen($code)); + $i += mb_strlen((string) $sequence) - 1; + $length = mb_strlen($format); + $char = $sequence; + } + + $result .= $char; + } + + return $result; + } + + /** + * List of replacements from date() format to isoFormat(). + * + * @return array + */ + public static function getFormatsToIsoReplacements() + { + static $replacements = null; + + if ($replacements === null) { + $replacements = [ + 'd' => true, + 'D' => 'ddd', + 'j' => true, + 'l' => 'dddd', + 'N' => true, + 'S' => function ($date) { + $day = $date->rawFormat('j'); + + return str_replace((string) $day, '', $date->isoFormat('Do')); + }, + 'w' => true, + 'z' => true, + 'W' => true, + 'F' => 'MMMM', + 'm' => true, + 'M' => 'MMM', + 'n' => true, + 't' => true, + 'L' => true, + 'o' => true, + 'Y' => true, + 'y' => true, + 'a' => 'a', + 'A' => 'A', + 'B' => true, + 'g' => true, + 'G' => true, + 'h' => true, + 'H' => true, + 'i' => true, + 's' => true, + 'u' => true, + 'v' => true, + 'E' => true, + 'I' => true, + 'O' => true, + 'P' => true, + 'Z' => true, + 'c' => true, + 'r' => true, + 'U' => true, + 'T' => true, + ]; + } + + return $replacements; + } + + /** + * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) + * but translate words whenever possible (months, day names, etc.) using the current locale. + * + * @param string $format + * + * @return string + */ + public function translatedFormat(string $format): string + { + $replacements = static::getFormatsToIsoReplacements(); + $context = ''; + $isoFormat = ''; + $length = mb_strlen($format); + + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($format, $i, 1); + + if ($char === '\\') { + $replacement = mb_substr($format, $i, 2); + $isoFormat .= $replacement; + $i++; + + continue; + } + + if (!isset($replacements[$char])) { + $replacement = preg_match('/^[A-Za-z]$/', $char) ? "\\$char" : $char; + $isoFormat .= $replacement; + $context .= $replacement; + + continue; + } + + $replacement = $replacements[$char]; + + if ($replacement === true) { + static $contextReplacements = null; + + if ($contextReplacements === null) { + $contextReplacements = [ + 'm' => 'MM', + 'd' => 'DD', + 't' => 'D', + 'j' => 'D', + 'N' => 'e', + 'w' => 'e', + 'n' => 'M', + 'o' => 'YYYY', + 'Y' => 'YYYY', + 'y' => 'YY', + 'g' => 'h', + 'G' => 'H', + 'h' => 'hh', + 'H' => 'HH', + 'i' => 'mm', + 's' => 'ss', + ]; + } + + $isoFormat .= '['.$this->rawFormat($char).']'; + $context .= $contextReplacements[$char] ?? ' '; + + continue; + } + + if ($replacement instanceof Closure) { + $replacement = '['.$replacement($this).']'; + $isoFormat .= $replacement; + $context .= $replacement; + + continue; + } + + $isoFormat .= $replacement; + $context .= $replacement; + } + + return $this->isoFormat($isoFormat, $context); + } + + /** + * Returns the offset hour and minute formatted with +/- and a given separator (":" by default). + * For example, if the time zone is 9 hours 30 minutes, you'll get "+09:30", with "@@" as first + * argument, "+09@@30", with "" as first argument, "+0930". Negative offset will return something + * like "-12:00". + * + * @param string $separator string to place between hours and minutes (":" by default) + * + * @return string + */ + public function getOffsetString($separator = ':') + { + $second = $this->getOffset(); + $symbol = $second < 0 ? '-' : '+'; + $minute = abs($second) / static::SECONDS_PER_MINUTE; + $hour = str_pad((string) floor($minute / static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + $minute = str_pad((string) (((int) $minute) % static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + + return "$symbol$hour$separator$minute"; + } + + protected static function executeStaticCallable($macro, ...$parameters) + { + return static::bindMacroContext(null, function () use (&$macro, &$parameters) { + if ($macro instanceof Closure) { + $boundMacro = @Closure::bind($macro, null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + }); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + if (!static::hasMacro($method)) { + foreach (static::getGenericMacros() as $callback) { + try { + return static::executeStaticCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException $exception) { + continue; + } + } + if (static::isStrictModeEnabled()) { + throw new UnknownMethodException(sprintf('%s::%s', static::class, $method)); + } + + return null; + } + + return static::executeStaticCallable(static::$globalMacros[$method], ...$parameters); + } + + /** + * Set specified unit to new given value. + * + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param int $value new value for given unit + * + * @return static + */ + public function setUnit($unit, $value = null) + { + $unit = static::singularUnit($unit); + $dateUnits = ['year', 'month', 'day']; + if (\in_array($unit, $dateUnits)) { + return $this->setDate(...array_map(function ($name) use ($unit, $value) { + return (int) ($name === $unit ? $value : $this->$name); + }, $dateUnits)); + } + + $units = ['hour', 'minute', 'second', 'micro']; + if ($unit === 'millisecond' || $unit === 'milli') { + $value *= 1000; + $unit = 'micro'; + } elseif ($unit === 'microsecond') { + $unit = 'micro'; + } + + return $this->setTime(...array_map(function ($name) use ($unit, $value) { + return (int) ($name === $unit ? $value : $this->$name); + }, $units)); + } + + /** + * Returns standardized singular of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function singularUnit(string $unit): string + { + $unit = rtrim(mb_strtolower($unit), 's'); + + if ($unit === 'centurie') { + return 'century'; + } + + if ($unit === 'millennia') { + return 'millennium'; + } + + return $unit; + } + + /** + * Returns standardized plural of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function pluralUnit(string $unit): string + { + $unit = rtrim(strtolower($unit), 's'); + + if ($unit === 'century') { + return 'centuries'; + } + + if ($unit === 'millennium' || $unit === 'millennia') { + return 'millennia'; + } + + return "{$unit}s"; + } + + protected function executeCallable($macro, ...$parameters) + { + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + protected function executeCallableWithContext($macro, ...$parameters) + { + return static::bindMacroContext($this, function () use (&$macro, &$parameters) { + return $this->executeCallable($macro, ...$parameters); + }); + } + + protected static function getGenericMacros() + { + foreach (static::$globalGenericMacros as $list) { + foreach ($list as $macro) { + yield $macro; + } + } + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable + * + * @return mixed + */ + public function __call($method, $parameters) + { + $diffSizes = [ + // @mode diffForHumans + 'short' => true, + // @mode diffForHumans + 'long' => false, + ]; + $diffSyntaxModes = [ + // @call diffForHumans + 'Absolute' => CarbonInterface::DIFF_ABSOLUTE, + // @call diffForHumans + 'Relative' => CarbonInterface::DIFF_RELATIVE_AUTO, + // @call diffForHumans + 'RelativeToNow' => CarbonInterface::DIFF_RELATIVE_TO_NOW, + // @call diffForHumans + 'RelativeToOther' => CarbonInterface::DIFF_RELATIVE_TO_OTHER, + ]; + $sizePattern = implode('|', array_keys($diffSizes)); + $syntaxPattern = implode('|', array_keys($diffSyntaxModes)); + + if (preg_match("/^(?$sizePattern)(?$syntaxPattern)DiffForHumans$/", $method, $match)) { + $dates = array_filter($parameters, function ($parameter) { + return $parameter instanceof DateTimeInterface; + }); + $other = null; + + if (\count($dates)) { + $key = key($dates); + $other = current($dates); + array_splice($parameters, $key, 1); + } + + return $this->diffForHumans($other, $diffSyntaxModes[$match['syntax']], $diffSizes[$match['size']], ...$parameters); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + $unit = rtrim($method, 's'); + + if (str_starts_with($unit, 'is')) { + $word = substr($unit, 2); + + if (\in_array($word, static::$days, true)) { + return $this->isDayOfWeek($word); + } + + switch ($word) { + // @call is Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + case 'Utc': + case 'UTC': + return $this->utc; + // @call is Check if the current instance has non-UTC timezone. + case 'Local': + return $this->local; + // @call is Check if the current instance is a valid date. + case 'Valid': + return $this->year !== 0; + // @call is Check if the current instance is in a daylight saving time. + case 'DST': + return $this->dst; + } + } + + $action = substr($unit, 0, 3); + $overflow = null; + + if ($action === 'set') { + $unit = strtolower(substr($unit, 3)); + } + + if (\in_array($unit, static::$units, true)) { + return $this->setUnit($unit, ...$parameters); + } + + if ($action === 'add' || $action === 'sub') { + $unit = substr($unit, 3); + + if (str_starts_with($unit, 'Real')) { + $unit = static::singularUnit(substr($unit, 4)); + + return $this->{"{$action}RealUnit"}($unit, ...$parameters); + } + + if (preg_match('/^(Month|Quarter|Year|Decade|Century|Centurie|Millennium|Millennia)s?(No|With|Without|WithNo)Overflow$/', $unit, $match)) { + $unit = $match[1]; + $overflow = $match[2] === 'With'; + } + + $unit = static::singularUnit($unit); + } + + if (static::isModifiableUnit($unit)) { + return $this->{"{$action}Unit"}($unit, $this->getMagicParameter($parameters, 0, 'value', 1), $overflow); + } + + $sixFirstLetters = substr($unit, 0, 6); + $factor = -1; + + if ($sixFirstLetters === 'isLast') { + $sixFirstLetters = 'isNext'; + $factor = 1; + } + + if ($sixFirstLetters === 'isNext') { + $lowerUnit = strtolower(substr($unit, 6)); + + if (static::isModifiableUnit($lowerUnit)) { + return $this->copy()->addUnit($lowerUnit, $factor, false)->isSameUnit($lowerUnit, ...$parameters); + } + } + + if ($sixFirstLetters === 'isSame') { + try { + return $this->isSameUnit(strtolower(substr($unit, 6)), ...$parameters); + } catch (BadComparisonUnitException $exception) { + // Try next + } + } + + if (str_starts_with($unit, 'isCurrent')) { + try { + return $this->isCurrentUnit(strtolower(substr($unit, 9))); + } catch (BadComparisonUnitException | BadMethodCallException $exception) { + // Try next + } + } + + if (str_ends_with($method, 'Until')) { + try { + $unit = static::singularUnit(substr($method, 0, -5)); + + return $this->range( + $this->getMagicParameter($parameters, 0, 'endDate', $this), + $this->getMagicParameter($parameters, 1, 'factor', 1), + $unit + ); + } catch (InvalidArgumentException $exception) { + // Try macros + } + } + + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + $macro = $this->getLocalMacro($method); + + if (!$macro) { + foreach ([$this->localGenericMacros ?: [], static::getGenericMacros()] as $list) { + foreach ($list as $callback) { + try { + return $this->executeCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException $exception) { + continue; + } + } + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnknownMethodException($method); + } + + return null; + } + + return $this->executeCallable($macro, ...$parameters); + }); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/DeprecatedProperties.php b/libraries/Carbon/src/Carbon/Traits/DeprecatedProperties.php new file mode 100644 index 00000000000..fc72e4463b7 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/DeprecatedProperties.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +trait DeprecatedProperties +{ + /** + * the day of week in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('MMM') instead. + * Deprecated since 2.55.0 + */ + public $localeDayOfWeek; + + /** + * the abbreviated day of week in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('dddd') instead. + * Deprecated since 2.55.0 + */ + public $shortLocaleDayOfWeek; + + /** + * the month in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('ddd') instead. + * Deprecated since 2.55.0 + */ + public $localeMonth; + + /** + * the abbreviated month in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('MMMM') instead. + * Deprecated since 2.55.0 + */ + public $shortLocaleMonth; +} diff --git a/libraries/Carbon/src/Carbon/Traits/Difference.php b/libraries/Carbon/src/Carbon/Traits/Difference.php new file mode 100644 index 00000000000..4d5be121276 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Difference.php @@ -0,0 +1,1182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\Carbon; +use EDD\Vendor\Carbon\CarbonImmutable; +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\CarbonInterval; +use EDD\Vendor\Carbon\CarbonPeriod; +use EDD\Vendor\Carbon\Translator; +use Closure; +use DateInterval; +use DateTimeInterface; +use ReturnTypeWillChange; + +/** + * Trait Difference. + * + * Depends on the following methods: + * + * @method bool lessThan($date) + * @method static copy() + * @method static resolveCarbon($date = null) + * @method static Translator translator() + */ +trait Difference +{ + /** + * @codeCoverageIgnore + * + * @param CarbonInterval $diff + */ + protected static function fixNegativeMicroseconds(CarbonInterval $diff) + { + if ($diff->s !== 0 || $diff->i !== 0 || $diff->h !== 0 || $diff->d !== 0 || $diff->m !== 0 || $diff->y !== 0) { + $diff->f = (round($diff->f * 1000000) + 1000000) / 1000000; + $diff->s--; + + if ($diff->s < 0) { + $diff->s += 60; + $diff->i--; + + if ($diff->i < 0) { + $diff->i += 60; + $diff->h--; + + if ($diff->h < 0) { + $diff->h += 24; + $diff->d--; + + if ($diff->d < 0) { + $diff->d += 30; + $diff->m--; + + if ($diff->m < 0) { + $diff->m += 12; + $diff->y--; + } + } + } + } + } + + return; + } + + $diff->f *= -1; + $diff->invert(); + } + + /** + * @param DateInterval $diff + * @param bool $absolute + * + * @return CarbonInterval + */ + protected static function fixDiffInterval(DateInterval $diff, $absolute, array $skip = []) + { + $diff = CarbonInterval::instance($diff, $skip); + + // Work-around for https://bugs.php.net/bug.php?id=77145 + // @codeCoverageIgnoreStart + if ($diff->f > 0 && $diff->y === -1 && $diff->m === 11 && $diff->d >= 27 && $diff->h === 23 && $diff->i === 59 && $diff->s === 59) { + $diff->y = 0; + $diff->m = 0; + $diff->d = 0; + $diff->h = 0; + $diff->i = 0; + $diff->s = 0; + $diff->f = (1000000 - round($diff->f * 1000000)) / 1000000; + $diff->invert(); + } elseif ($diff->f < 0) { + static::fixNegativeMicroseconds($diff); + } + // @codeCoverageIgnoreEnd + + if ($absolute && $diff->invert) { + $diff->invert(); + } + + return $diff; + } + + /** + * Get the difference as a DateInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return DateInterval + */ + #[ReturnTypeWillChange] + public function diff($date = null, $absolute = false) + { + $other = $this->resolveCarbon($date); + + // Work-around for https://bugs.php.net/bug.php?id=81458 + // It was initially introduced for https://bugs.php.net/bug.php?id=80998 + // The very specific case of 80998 was fixed in PHP 8.1beta3, but it introduced 81458 + // So we still need to keep this for now + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=') && $other->tz !== $this->tz) { + $other = $other->avoidMutation()->tz($this->tz); + } + // @codeCoverageIgnoreEnd + + return parent::diff($other, (bool) $absolute); + } + + /** + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = []) + { + return static::fixDiffInterval($this->diff($this->resolveCarbon($date), $absolute), $absolute, $skip); + } + + /** + * Get the difference in years + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%y'); + } + + /** + * Get the difference in quarters rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInQuarters($date = null, $absolute = true) + { + return (int) ($this->diffInMonths($date, $absolute) / static::MONTHS_PER_QUARTER); + } + + /** + * Get the difference in months rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true) + { + $date = $this->resolveCarbon($date)->avoidMutation()->tz($this->tz); + + [$yearStart, $monthStart, $dayStart] = explode('-', $this->format('Y-m-dHisu')); + [$yearEnd, $monthEnd, $dayEnd] = explode('-', $date->format('Y-m-dHisu')); + + $diff = (((int) $yearEnd) - ((int) $yearStart)) * static::MONTHS_PER_YEAR + + ((int) $monthEnd) - ((int) $monthStart); + + if ($diff > 0) { + $diff -= ($dayStart > $dayEnd ? 1 : 0); + } elseif ($diff < 0) { + $diff += ($dayStart < $dayEnd ? 1 : 0); + } + + return $absolute ? abs($diff) : $diff; + } + + /** + * Get the difference in weeks rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true) + { + return (int) ($this->diffInDays($date, $absolute) / static::DAYS_PER_WEEK); + } + + /** + * Get the difference in days rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true) + { + return $this->getIntervalDayDiff($this->diff($this->resolveCarbon($date), $absolute)); + } + + /** + * Get the difference in days using a filter closure rounded down. + * + * @param Closure $callback + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::day(), $callback, $date, $absolute); + } + + /** + * Get the difference in hours using a filter closure rounded down. + * + * @param Closure $callback + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::hour(), $callback, $date, $absolute); + } + + /** + * Get the difference by the given interval using a filter closure. + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $inverse = false; + + if ($end < $start) { + $start = $end; + $end = $this; + $inverse = true; + } + + $options = CarbonPeriod::EXCLUDE_END_DATE | ($this->isMutable() ? 0 : CarbonPeriod::IMMUTABLE); + $diff = $ci->toPeriod($start, $end, $options)->filter($callback)->count(); + + return $inverse && !$absolute ? -$diff : $diff; + } + + /** + * Get the difference in weekdays rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(static function (CarbonInterface $date) { + return $date->isWeekday(); + }, $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), $absolute); + } + + /** + * Get the difference in weekend days using a filter rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(static function (CarbonInterface $date) { + return $date->isWeekend(); + }, $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), $absolute); + } + + /** + * Get the difference in hours rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in hours rounded down using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in minutes rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in minutes rounded down using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in seconds rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true) + { + $diff = $this->diff($date); + + if ($diff->days === 0) { + $diff = static::fixDiffInterval($diff, $absolute); + } + + $value = (((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + $diff->s; + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in microseconds. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = true) + { + $diff = $this->diff($date); + $value = (int) round(((((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + ($diff->f + $diff->s)) * static::MICROSECONDS_PER_SECOND); + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in milliseconds rounded down. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = true) + { + return (int) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + + /** + * Get the difference in seconds using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true) + { + /** @var CarbonInterface $date */ + $date = $this->resolveCarbon($date); + $value = $date->getTimestamp() - $this->getTimestamp(); + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in microseconds using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true) + { + /** @var CarbonInterface $date */ + $date = $this->resolveCarbon($date); + $value = ($date->timestamp - $this->timestamp) * static::MICROSECONDS_PER_SECOND + + $date->micro - $this->micro; + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in milliseconds rounded down using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = true) + { + return (int) ($this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + + /** + * Get the difference in seconds as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInSeconds($date = null, $absolute = true) + { + return (float) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND); + } + + /** + * Get the difference in minutes as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMinutes($date = null, $absolute = true) + { + return $this->floatDiffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; + } + + /** + * Get the difference in hours as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInHours($date = null, $absolute = true) + { + return $this->floatDiffInMinutes($date, $absolute) / static::MINUTES_PER_HOUR; + } + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInDays($date = null, $absolute = true) + { + $hoursDiff = $this->floatDiffInHours($date, $absolute); + $interval = $this->diff($date, $absolute); + + if ($interval->y === 0 && $interval->m === 0 && $interval->d === 0) { + return $hoursDiff / static::HOURS_PER_DAY; + } + + $daysDiff = $this->getIntervalDayDiff($interval); + + return $daysDiff + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; + } + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInWeeks($date = null, $absolute = true) + { + return $this->floatDiffInDays($date, $absolute) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in months as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMonths($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $monthsDiff = $start->diffInMonths($end); + /** @var EDD\Vendor\Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var EDD\Vendor\Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ + $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); + + if ($startOfMonthAfterFloorEnd > $end) { + return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInMonth); + } + + return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInDays($end) / $end->daysInMonth); + } + + /** + * Get the difference in year as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInYears($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $yearsDiff = $start->diffInYears($end); + /** @var EDD\Vendor\Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var EDD\Vendor\Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ + $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); + + if ($startOfYearAfterFloorEnd > $end) { + return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInYear); + } + + return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInDays($end) / $end->daysInYear); + } + + /** + * Get the difference in seconds as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealSeconds($date = null, $absolute = true) + { + return $this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND; + } + + /** + * Get the difference in minutes as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMinutes($date = null, $absolute = true) + { + return $this->floatDiffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; + } + + /** + * Get the difference in hours as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealHours($date = null, $absolute = true) + { + return $this->floatDiffInRealMinutes($date, $absolute) / static::MINUTES_PER_HOUR; + } + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealDays($date = null, $absolute = true) + { + $date = $this->resolveUTC($date); + $utc = $this->avoidMutation()->utc(); + $hoursDiff = $utc->floatDiffInRealHours($date, $absolute); + + return ($hoursDiff < 0 ? -1 : 1) * $utc->diffInDays($date) + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; + } + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealWeeks($date = null, $absolute = true) + { + return $this->floatDiffInRealDays($date, $absolute) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in months as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMonths($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $monthsDiff = $start->diffInMonths($end); + /** @var EDD\Vendor\Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var EDD\Vendor\Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ + $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); + + if ($startOfMonthAfterFloorEnd > $end) { + return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInMonth); + } + + return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInMonth); + } + + /** + * Get the difference in year as float (microsecond-precision) using timestamps. + * + * @param \EDD\Vendor\Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealYears($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $yearsDiff = $start->diffInYears($end); + /** @var EDD\Vendor\Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var EDD\Vendor\Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ + $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); + + if ($startOfYearAfterFloorEnd > $end) { + return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInYear); + } + + return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInYear); + } + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight() + { + return $this->diffInSeconds($this->avoidMutation()->startOfDay()); + } + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay() + { + return $this->diffInSeconds($this->avoidMutation()->endOfDay()); + } + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @example + * ``` + * echo Carbon::tomorrow()->diffForHumans() . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 2]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday()) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; + * ``` + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + /* @var CarbonInterface $this */ + if (\is_array($other)) { + $other['syntax'] = \array_key_exists('syntax', $other) ? $other['syntax'] : $syntax; + $syntax = $other; + $other = $syntax['other'] ?? null; + } + + $intSyntax = &$syntax; + if (\is_array($syntax)) { + $syntax['syntax'] = $syntax['syntax'] ?? null; + $intSyntax = &$syntax['syntax']; + } + $intSyntax = (int) ($intSyntax ?? static::DIFF_RELATIVE_AUTO); + $intSyntax = $intSyntax === static::DIFF_RELATIVE_AUTO && $other === null ? static::DIFF_RELATIVE_TO_NOW : $intSyntax; + + $parts = min(7, max(1, (int) $parts)); + $skip = \is_array($syntax) ? ($syntax['skip'] ?? []) : []; + + return $this->diffAsCarbonInterval($other, false, (array) $skip) + ->setLocalTranslator($this->getLocalTranslator()) + ->forHumans($syntax, (bool) $short, $parts, $options ?? $this->localHumanDiffOptions ?? static::getHumanDiffOptions()); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function from($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->diffForHumans($other, $syntax, $short, $parts, $options); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + */ + public function since($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->diffForHumans($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * When comparing a value in the past to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the future to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the past to another value: + * 1 hour after + * 5 months after + * + * When comparing a value in the future to another value: + * 1 hour before + * 5 months before + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function to($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + if (!$syntax && !$other) { + $syntax = CarbonInterface::DIFF_RELATIVE_TO_NOW; + } + + return $this->resolveCarbon($other)->diffForHumans($this, $syntax, $short, $parts, $options); + } + + /** + * @alias to + * + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function until($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->to($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function fromNow($syntax = null, $short = false, $parts = 1, $options = null) + { + $other = null; + + if ($syntax instanceof DateTimeInterface) { + [$other, $syntax, $short, $parts, $options] = array_pad(\func_get_args(), 5, null); + } + + return $this->from($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function toNow($syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->to(null, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function ago($syntax = null, $short = false, $parts = 1, $options = null) + { + $other = null; + + if ($syntax instanceof DateTimeInterface) { + [$other, $syntax, $short, $parts, $options] = array_pad(\func_get_args(), 5, null); + } + + return $this->from($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @return string + */ + public function timespan($other = null, $timezone = null) + { + if (!$other instanceof DateTimeInterface) { + $other = static::parse($other, $timezone); + } + + return $this->diffForHumans($other, [ + 'join' => ', ', + 'syntax' => CarbonInterface::DIFF_ABSOLUTE, + 'options' => CarbonInterface::NO_ZERO_DIFF, + 'parts' => -1, + ]); + } + + /** + * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, + * or a calendar date (e.g. "10/29/2017") otherwise. + * + * Language, date and time formats will change according to the current locale. + * + * @param EDD\Vendor\Carbon|\DateTimeInterface|string|null $referenceTime + * @param array $formats + * + * @return string + */ + public function calendar($referenceTime = null, array $formats = []) + { + /** @var CarbonInterface $current */ + $current = $this->avoidMutation()->startOfDay(); + /** @var CarbonInterface $other */ + $other = $this->resolveCarbon($referenceTime)->avoidMutation()->setTimezone($this->getTimezone())->startOfDay(); + $diff = $other->diffInDays($current, false); + $format = $diff < -6 ? 'sameElse' : ( + $diff < -1 ? 'lastWeek' : ( + $diff < 0 ? 'lastDay' : ( + $diff < 1 ? 'sameDay' : ( + $diff < 2 ? 'nextDay' : ( + $diff < 7 ? 'nextWeek' : 'sameElse' + ) + ) + ) + ) + ); + $format = array_merge($this->getCalendarFormats(), $formats)[$format]; + if ($format instanceof Closure) { + $format = $format($current, $other) ?? ''; + } + + return $this->isoFormat((string) $format); + } + + private function getIntervalDayDiff(DateInterval $interval): int + { + $daysDiff = (int) $interval->format('%a'); + $sign = $interval->format('%r') === '-' ? -1 : 1; + + if (\is_int($interval->days) && + $interval->y === 0 && + $interval->m === 0 && + version_compare(PHP_VERSION, '8.1.0-dev', '<') && + abs($interval->d - $daysDiff) === 1 + ) { + $daysDiff = abs($interval->d); // @codeCoverageIgnore + } + + return $daysDiff * $sign; + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/IntervalRounding.php b/libraries/Carbon/src/Carbon/Traits/IntervalRounding.php new file mode 100644 index 00000000000..aa564a38db3 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/IntervalRounding.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\CarbonInterval; +use EDD\Vendor\Carbon\Exceptions\InvalidIntervalException; +use DateInterval; + +/** + * Trait to call rounding methods to interval or the interval of a period. + */ +trait IntervalRounding +{ + protected function callRoundMethod(string $method, array $parameters) + { + $action = substr($method, 0, 4); + + if ($action !== 'ceil') { + $action = substr($method, 0, 5); + } + + if (\in_array($action, ['round', 'floor', 'ceil'])) { + return $this->{$action.'Unit'}(substr($method, \strlen($action)), ...$parameters); + } + + return null; + } + + protected function roundWith($precision, $function) + { + $unit = 'second'; + + if ($precision instanceof DateInterval) { + $precision = (string) CarbonInterval::instance($precision, [], true); + } + + if (\is_string($precision) && preg_match('/^\s*(?\d+)?\s*(?\w+)(?\W.*)?$/', $precision, $match)) { + if (trim($match['other'] ?? '') !== '') { + throw new InvalidIntervalException('Rounding is only possible with single unit intervals.'); + } + + $precision = (int) ($match['precision'] ?: 1); + $unit = $match['unit']; + } + + return $this->roundUnit($unit, $precision, $function); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/IntervalStep.php b/libraries/Carbon/src/Carbon/Traits/IntervalStep.php new file mode 100644 index 00000000000..1783f510fcc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/IntervalStep.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\Carbon; +use EDD\Vendor\Carbon\CarbonImmutable; +use EDD\Vendor\Carbon\CarbonInterface; +use Closure; +use DateTimeImmutable; +use DateTimeInterface; + +trait IntervalStep +{ + /** + * Step to apply instead of a fixed interval to get the new date. + * + * @var Closure|null + */ + protected $step; + + /** + * Get the dynamic step in use. + * + * @return Closure + */ + public function getStep(): ?Closure + { + return $this->step; + } + + /** + * Set a step to apply instead of a fixed interval to get the new date. + * + * Or pass null to switch to fixed interval. + * + * @param Closure|null $step + */ + public function setStep(?Closure $step): void + { + $this->step = $step; + } + + /** + * Take a date and apply either the step if set, or the current interval else. + * + * The interval/step is applied negatively (typically subtraction instead of addition) if $negated is true. + * + * @param DateTimeInterface $dateTime + * @param bool $negated + * + * @return CarbonInterface + */ + public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface + { + /** @var CarbonInterface $carbonDate */ + $carbonDate = $dateTime instanceof CarbonInterface ? $dateTime : $this->resolveCarbon($dateTime); + + if ($this->step) { + return $carbonDate->setDateTimeFrom(($this->step)($carbonDate->avoidMutation(), $negated)); + } + + if ($negated) { + return $carbonDate->rawSub($this); + } + + return $carbonDate->rawAdd($this); + } + + /** + * Convert DateTimeImmutable instance to CarbonImmutable instance and DateTime instance to EDD\Vendor\Carbon instance. + * + * @param DateTimeInterface $dateTime + * + * @return EDD\Vendor\Carbon|CarbonImmutable + */ + private function resolveCarbon(DateTimeInterface $dateTime) + { + if ($dateTime instanceof DateTimeImmutable) { + return CarbonImmutable::instance($dateTime); + } + + return Carbon::instance($dateTime); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Localization.php b/libraries/Carbon/src/Carbon/Traits/Localization.php new file mode 100644 index 00000000000..a44a32975c0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Localization.php @@ -0,0 +1,840 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\Exceptions\InvalidTypeException; +use EDD\Vendor\Carbon\Exceptions\NotLocaleAwareException; +use EDD\Vendor\Carbon\Language; +use EDD\Vendor\Carbon\Translator; +use EDD\Vendor\Carbon\TranslatorStrongTypeInterface; +use Closure; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBagInterface; +use EDD\Vendor\Symfony\Component\Translation\TranslatorInterface; +use EDD\Vendor\Symfony\Contracts\Translation\LocaleAwareInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface as ContractsTranslatorInterface; + +// @codeCoverageIgnoreStart +if (interface_exists('Symfony\\Contracts\\Translation\\TranslatorInterface') && + !interface_exists('Symfony\\Component\\Translation\\TranslatorInterface') +) { + class_alias( + 'Symfony\\Contracts\\Translation\\TranslatorInterface', + 'Symfony\\Component\\Translation\\TranslatorInterface' + ); +} +// @codeCoverageIgnoreEnd + +/** + * Trait Localization. + * + * Embed default and locale translators and translation base methods. + */ +trait Localization +{ + /** + * Default translator. + * + * @var \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * Specific translator of the current instance. + * + * @var \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface + */ + protected $localTranslator; + + /** + * Options for diffForHumans(). + * + * @var int + */ + protected static $humanDiffOptions = CarbonInterface::NO_ZERO_DIFF; + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions) + { + static::$humanDiffOptions = $humanDiffOptions; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; + } + + /** + * Return default humanDiff() options (merged flags as integer). + * + * @return int + */ + public static function getHumanDiffOptions() + { + return static::$humanDiffOptions; + } + + /** + * Get the default translator instance in use. + * + * @return \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + + /** + * Set the default translator instance to use. + * + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator) + { + static::$translator = $translator; + } + + /** + * Return true if the current instance has its own translator. + * + * @return bool + */ + public function hasLocalTranslator() + { + return isset($this->localTranslator); + } + + /** + * Get the translator of the current instance or the default if none set. + * + * @return \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface + */ + public function getLocalTranslator() + { + return $this->localTranslator ?: static::translator(); + } + + /** + * Set the translator for the current instance. + * + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator + * + * @return $this + */ + public function setLocalTranslator(TranslatorInterface $translator) + { + $this->localTranslator = $translator; + + return $this; + } + + /** + * Returns raw translation message for a given key. + * + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * + * @return string + */ + public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) + { + if (!($translator instanceof TranslatorBagInterface && $translator instanceof TranslatorInterface)) { + throw new InvalidTypeException( + 'Translator does not implement '.TranslatorInterface::class.' and '.TranslatorBagInterface::class.'. '. + (\is_object($translator) ? \get_class($translator) : \gettype($translator)).' has been given.' + ); + } + + if (!$locale && $translator instanceof LocaleAwareInterface) { + $locale = $translator->getLocale(); + } + + $result = self::getFromCatalogue($translator, $translator->getCatalogue($locale), $key); + + return $result === $key ? $default : $result; + } + + /** + * Returns raw translation message for a given key. + * + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * + * @return string + */ + public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) + { + return static::getTranslationMessageWith($translator ?: $this->getLocalTranslator(), $key, $locale, $default); + } + + /** + * Translate using translation string or callback available. + * + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface $translator + * @param string $key + * @param array $parameters + * @param null $number + * + * @return string + */ + public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string + { + $message = static::getTranslationMessageWith($translator, $key, null, $key); + if ($message instanceof Closure) { + return (string) $message(...array_values($parameters)); + } + + if ($number !== null) { + $parameters['%count%'] = $number; + } + if (isset($parameters['%count%'])) { + $parameters[':count'] = $parameters['%count%']; + } + + // @codeCoverageIgnoreStart + $choice = $translator instanceof ContractsTranslatorInterface + ? $translator->trans($key, $parameters) + : $translator->transChoice($key, $number, $parameters); + // @codeCoverageIgnoreEnd + + return (string) $choice; + } + + /** + * Translate using translation string or callback available. + * + * @param string $key + * @param array $parameters + * @param string|int|float|null $number + * @param \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface|null $translator + * @param bool $altNumbers + * + * @return string + */ + public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string + { + $translation = static::translateWith($translator ?: $this->getLocalTranslator(), $key, $parameters, $number); + + if ($number !== null && $altNumbers) { + return str_replace($number, $this->translateNumber($number), $translation); + } + + return $translation; + } + + /** + * Returns the alternative number for a given integer if available in the current locale. + * + * @param int $number + * + * @return string + */ + public function translateNumber(int $number): string + { + $translateKey = "alt_numbers.$number"; + $symbol = $this->translate($translateKey); + + if ($symbol !== $translateKey) { + return $symbol; + } + + if ($number > 99 && $this->translate('alt_numbers.99') !== 'alt_numbers.99') { + $start = ''; + foreach ([10000, 1000, 100] as $exp) { + $key = "alt_numbers_pow.$exp"; + if ($number >= $exp && $number < $exp * 10 && ($pow = $this->translate($key)) !== $key) { + $unit = floor($number / $exp); + $number -= $unit * $exp; + $start .= ($unit > 1 ? $this->translate("alt_numbers.$unit") : '').$pow; + } + } + $result = ''; + while ($number) { + $chunk = $number % 100; + $result = $this->translate("alt_numbers.$chunk").$result; + $number = floor($number / 100); + } + + return "$start$result"; + } + + if ($number > 9 && $this->translate('alt_numbers.9') !== 'alt_numbers.9') { + $result = ''; + while ($number) { + $chunk = $number % 10; + $result = $this->translate("alt_numbers.$chunk").$result; + $number = floor($number / 10); + } + + return $result; + } + + return (string) $number; + } + + /** + * Translate a time string from a locale to an other. + * + * @param string $timeString date/time/duration string to translate (may also contain English) + * @param string|null $from input locale of the $timeString parameter (`Carbon::getLocale()` by default) + * @param string|null $to output locale of the result returned (`"en"` by default) + * @param int $mode specify what to translate with options: + * - CarbonInterface::TRANSLATE_ALL (default) + * - CarbonInterface::TRANSLATE_MONTHS + * - CarbonInterface::TRANSLATE_DAYS + * - CarbonInterface::TRANSLATE_UNITS + * - CarbonInterface::TRANSLATE_MERIDIEM + * You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS + * + * @return string + */ + public static function translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) + { + // Fallback source and destination locales + $from = $from ?: static::getLocale(); + $to = $to ?: 'en'; + + if ($from === $to) { + return $timeString; + } + + // Standardize apostrophe + $timeString = strtr($timeString, ['’' => "'"]); + + $fromTranslations = []; + $toTranslations = []; + + foreach (['from', 'to'] as $key) { + $language = $$key; + $translator = Translator::get($language); + $translations = $translator->getMessages(); + + if (!isset($translations[$language])) { + return $timeString; + } + + $translationKey = $key.'Translations'; + $messages = $translations[$language]; + $months = $messages['months'] ?? []; + $weekdays = $messages['weekdays'] ?? []; + $meridiem = $messages['meridiem'] ?? ['AM', 'PM']; + + if (isset($messages['ordinal_words'])) { + $timeString = self::replaceOrdinalWords( + $timeString, + $key === 'from' ? array_flip($messages['ordinal_words']) : $messages['ordinal_words'] + ); + } + + if ($key === 'from') { + foreach (['months', 'weekdays'] as $variable) { + $list = $messages[$variable.'_standalone'] ?? null; + + if ($list) { + foreach ($$variable as $index => &$name) { + $name .= '|'.$messages[$variable.'_standalone'][$index]; + } + } + } + } + + $$translationKey = array_merge( + $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($months, 12, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($messages['months_short'] ?? [], 12, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($weekdays, 7, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($messages['weekdays_short'] ?? [], 7, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DIFF ? static::translateWordsByKeys([ + 'diff_now', + 'diff_today', + 'diff_yesterday', + 'diff_tomorrow', + 'diff_before_yesterday', + 'diff_after_tomorrow', + ], $messages, $key) : [], + $mode & CarbonInterface::TRANSLATE_UNITS ? static::translateWordsByKeys([ + 'year', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + ], $messages, $key) : [], + $mode & CarbonInterface::TRANSLATE_MERIDIEM ? array_map(function ($hour) use ($meridiem) { + if (\is_array($meridiem)) { + return $meridiem[$hour < 12 ? 0 : 1]; + } + + return $meridiem($hour, 0, false); + }, range(0, 23)) : [] + ); + } + + return substr(preg_replace_callback('/(?<=[\d\s+.\/,_-])('.implode('|', $fromTranslations).')(?=[\d\s+.\/,_-])/iu', function ($match) use ($fromTranslations, $toTranslations) { + [$chunk] = $match; + + foreach ($fromTranslations as $index => $word) { + if (preg_match("/^$word\$/iu", $chunk)) { + return $toTranslations[$index] ?? ''; + } + } + + return $chunk; // @codeCoverageIgnore + }, " $timeString "), 1, -1); + } + + /** + * Translate a time string from the current locale (`$date->locale()`) to an other. + * + * @param string $timeString time string to translate + * @param string|null $to output locale of the result returned ("en" by default) + * + * @return string + */ + public function translateTimeStringTo($timeString, $to = null) + { + return static::translateTimeString($timeString, $this->getTranslatorLocale(), $to); + } + + /** + * Get/set the locale for the current instance. + * + * @param string|null $locale + * @param string ...$fallbackLocales + * + * @return $this|string + */ + public function locale(string $locale = null, ...$fallbackLocales) + { + if ($locale === null) { + return $this->getTranslatorLocale(); + } + + if (!$this->localTranslator || $this->getTranslatorLocale($this->localTranslator) !== $locale) { + $translator = Translator::get($locale); + + if (!empty($fallbackLocales)) { + $translator->setFallbackLocales($fallbackLocales); + + foreach ($fallbackLocales as $fallbackLocale) { + $messages = Translator::get($fallbackLocale)->getMessages(); + + if (isset($messages[$fallbackLocale])) { + $translator->setMessages($fallbackLocale, $messages[$fallbackLocale]); + } + } + } + + $this->localTranslator = $translator; + } + + return $this; + } + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale() + { + return static::getLocaleAwareTranslator()->getLocale(); + } + + /** + * Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale) + { + return static::getLocaleAwareTranslator()->setLocale($locale) !== false; + } + + /** + * Set the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @param string $locale + */ + public static function setFallbackLocale($locale) + { + $translator = static::getTranslator(); + + if (method_exists($translator, 'setFallbackLocales')) { + $translator->setFallbackLocales([$locale]); + + if ($translator instanceof Translator) { + $preferredLocale = $translator->getLocale(); + $translator->setMessages($preferredLocale, array_replace_recursive( + $translator->getMessages()[$locale] ?? [], + Translator::get($locale)->getMessages()[$locale] ?? [], + $translator->getMessages($preferredLocale) + )); + } + } + } + + /** + * Get the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @return string|null + */ + public static function getFallbackLocale() + { + $translator = static::getTranslator(); + + if (method_exists($translator, 'getFallbackLocales')) { + return $translator->getFallbackLocales()[0] ?? null; + } + + return null; + } + + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * @param callable $func + * + * @return mixed + */ + public static function executeWithLocale($locale, $func) + { + $currentLocale = static::getLocale(); + $result = $func(static::setLocale($locale) ? static::getLocale() : false, static::translator()); + static::setLocale($currentLocale); + + return $result; + } + + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return ($newLocale && (($y = static::translateWith($translator, 'y')) !== 'y' && $y !== static::translateWith($translator, 'year'))) || ( + ($y = static::translateWith($translator, 'd')) !== 'd' && + $y !== static::translateWith($translator, 'day') + ) || ( + ($y = static::translateWith($translator, 'h')) !== 'h' && + $y !== static::translateWith($translator, 'hour') + ); + }); + } + + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + if (!$newLocale) { + return false; + } + + foreach (['ago', 'from_now', 'before', 'after'] as $key) { + if ($translator instanceof TranslatorBagInterface && + self::getFromCatalogue($translator, $translator->getCatalogue($newLocale), $key) instanceof Closure + ) { + continue; + } + + if ($translator->trans($key) === $key) { + return false; + } + } + + return true; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_now') !== 'diff_now' && + $translator->trans('diff_yesterday') !== 'diff_yesterday' && + $translator->trans('diff_tomorrow') !== 'diff_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' && + $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('period_recurrences') !== 'period_recurrences' && + $translator->trans('period_interval') !== 'period_interval' && + $translator->trans('period_start_date') !== 'period_start_date' && + $translator->trans('period_end_date') !== 'period_end_date'; + }); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales() + { + $translator = static::getLocaleAwareTranslator(); + + return $translator instanceof Translator + ? $translator->getAvailableLocales() + : [$translator->getLocale()]; + } + + /** + * Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * + * @return Language[] + */ + public static function getAvailableLocalesInfo() + { + $languages = []; + foreach (static::getAvailableLocales() as $id) { + $languages[$id] = new Language($id); + } + + return $languages; + } + + /** + * Initialize the default translator instance if necessary. + * + * @return \EDD\Vendor\Symfony\Component\Translation\TranslatorInterface + */ + protected static function translator() + { + if (static::$translator === null) { + static::$translator = Translator::get(); + } + + return static::$translator; + } + + /** + * Get the locale of a given translator. + * + * If null or omitted, current local translator is used. + * If no local translator is in use, current global translator is used. + * + * @param null $translator + * + * @return string|null + */ + protected function getTranslatorLocale($translator = null): ?string + { + if (\func_num_args() === 0) { + $translator = $this->getLocalTranslator(); + } + + $translator = static::getLocaleAwareTranslator($translator); + + return $translator ? $translator->getLocale() : null; + } + + /** + * Throw an error if passed object is not LocaleAwareInterface. + * + * @param LocaleAwareInterface|null $translator + * + * @return LocaleAwareInterface|null + */ + protected static function getLocaleAwareTranslator($translator = null) + { + if (\func_num_args() === 0) { + $translator = static::translator(); + } + + if ($translator && !($translator instanceof LocaleAwareInterface || method_exists($translator, 'getLocale'))) { + throw new NotLocaleAwareException($translator); // @codeCoverageIgnore + } + + return $translator; + } + + /** + * @param mixed $translator + * @param \EDD\Vendor\Symfony\Component\Translation\MessageCatalogueInterface $catalogue + * + * @return mixed + */ + private static function getFromCatalogue($translator, $catalogue, string $id, string $domain = 'messages') + { + return $translator instanceof TranslatorStrongTypeInterface + ? $translator->getFromCatalogue($catalogue, $id, $domain) // @codeCoverageIgnore + : $catalogue->get($id, $domain); + } + + /** + * Return the word cleaned from its translation codes. + * + * @param string $word + * + * @return string + */ + private static function cleanWordFromTranslationString($word) + { + $word = str_replace([':count', '%count', ':time'], '', $word); + $word = strtr($word, ['’' => "'"]); + $word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word); + + return trim($word); + } + + /** + * Translate a list of words. + * + * @param string[] $keys keys to translate. + * @param string[] $messages messages bag handling translations. + * @param string $key 'to' (to get the translation) or 'from' (to get the detection RegExp pattern). + * + * @return string[] + */ + private static function translateWordsByKeys($keys, $messages, $key): array + { + return array_map(function ($wordKey) use ($messages, $key) { + $message = $key === 'from' && isset($messages[$wordKey.'_regexp']) + ? $messages[$wordKey.'_regexp'] + : ($messages[$wordKey] ?? null); + + if (!$message) { + return '>>DO NOT REPLACE<<'; + } + + $parts = explode('|', $message); + + return $key === 'to' + ? self::cleanWordFromTranslationString(end($parts)) + : '(?:'.implode('|', array_map([static::class, 'cleanWordFromTranslationString'], $parts)).')'; + }, $keys); + } + + /** + * Get an array of translations based on the current date. + * + * @param callable $translation + * @param int $length + * @param string $timeString + * + * @return string[] + */ + private static function getTranslationArray($translation, $length, $timeString): array + { + $filler = '>>DO NOT REPLACE<<'; + + if (\is_array($translation)) { + return array_pad($translation, $length, $filler); + } + + $list = []; + $date = static::now(); + + for ($i = 0; $i < $length; $i++) { + $list[] = $translation($date, $timeString, $i) ?? $filler; + } + + return $list; + } + + private static function replaceOrdinalWords(string $timeString, array $ordinalWords): string + { + return preg_replace_callback('/(? + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +/** + * Trait Macros. + * + * Allows users to register macros within the EDD\Vendor\Carbon class. + */ +trait Macro +{ + use Mixin; + + /** + * The registered macros. + * + * @var array + */ + protected static $globalMacros = []; + + /** + * The registered generic macros. + * + * @var array + */ + protected static $globalGenericMacros = []; + + /** + * Register a custom macro. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * Carbon::macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo Carbon::yesterday()->hours(11)->userFormat(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$globalMacros[$name] = $macro; + } + + /** + * Remove all macros and generic macros. + */ + public static function resetMacros() + { + static::$globalMacros = []; + static::$globalGenericMacros = []; + } + + /** + * Register a custom macro. + * + * @param object|callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public static function genericMacro($macro, $priority = 0) + { + if (!isset(static::$globalGenericMacros[$priority])) { + static::$globalGenericMacros[$priority] = []; + krsort(static::$globalGenericMacros, SORT_NUMERIC); + } + + static::$globalGenericMacros[$priority][] = $macro; + } + + /** + * Checks if macro is registered globally. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$globalMacros[$name]); + } + + /** + * Get the raw callable macro registered globally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public static function getMacro($name) + { + return static::$globalMacros[$name] ?? null; + } + + /** + * Checks if macro is registered globally or locally. + * + * @param string $name + * + * @return bool + */ + public function hasLocalMacro($name) + { + return ($this->localMacros && isset($this->localMacros[$name])) || static::hasMacro($name); + } + + /** + * Get the raw callable macro registered globally or locally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public function getLocalMacro($name) + { + return ($this->localMacros ?? [])[$name] ?? static::getMacro($name); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/MagicParameter.php b/libraries/Carbon/src/Carbon/Traits/MagicParameter.php new file mode 100644 index 00000000000..ccde1e34244 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/MagicParameter.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +/** + * Trait MagicParameter. + * + * Allows to retrieve parameter in magic calls by index or name. + */ +trait MagicParameter +{ + private function getMagicParameter(array $parameters, int $index, string $key, $default) + { + if (\array_key_exists($index, $parameters)) { + return $parameters[$index]; + } + + if (\array_key_exists($key, $parameters)) { + return $parameters[$key]; + } + + return $default; + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Mixin.php b/libraries/Carbon/src/Carbon/Traits/Mixin.php new file mode 100644 index 00000000000..e5dc273e8ee --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Mixin.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\CarbonInterval; +use EDD\Vendor\Carbon\CarbonPeriod; +use Closure; +use Generator; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use Throwable; + +/** + * Trait Mixin. + * + * Allows mixing in entire classes with multiple macros. + */ +trait Mixin +{ + /** + * Stack of macro instance contexts. + * + * @var array + */ + protected static $macroContextStack = []; + + /** + * Mix another object into the class. + * + * @example + * ``` + * Carbon::mixin(new class { + * public function addMoon() { + * return function () { + * return $this->addDays(30); + * }; + * } + * public function subMoon() { + * return function () { + * return $this->subDays(30); + * }; + * } + * }); + * $fullMoon = Carbon::create('2018-12-22'); + * $nextFullMoon = $fullMoon->addMoon(); + * $blackMoon = Carbon::create('2019-01-06'); + * $previousBlackMoon = $blackMoon->subMoon(); + * echo "$nextFullMoon\n"; + * echo "$previousBlackMoon\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + \is_string($mixin) && trait_exists($mixin) + ? self::loadMixinTrait($mixin) + : self::loadMixinClass($mixin); + } + + /** + * @param object|string $mixin + * + * @throws ReflectionException + */ + private static function loadMixinClass($mixin) + { + $methods = (new ReflectionClass($mixin))->getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($method->isConstructor() || $method->isDestructor()) { + continue; + } + + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + + /** + * @param string $trait + */ + private static function loadMixinTrait($trait) + { + $context = eval(self::getAnonymousClassCodeForTrait($trait)); + $className = \get_class($context); + $baseClass = static::class; + + foreach (self::getMixableMethods($context) as $name) { + $closureBase = Closure::fromCallable([$context, $name]); + + static::macro($name, function (...$parameters) use ($closureBase, $className, $baseClass) { + $downContext = isset($this) ? ($this) : new $baseClass(); + $context = isset($this) ? $this->cast($className) : new $className(); + + try { + // @ is required to handle error if not converted into exceptions + $closure = @$closureBase->bindTo($context); + } catch (Throwable $throwable) { // @codeCoverageIgnore + $closure = $closureBase; // @codeCoverageIgnore + } + + // in case of errors not converted into exceptions + $closure = $closure ?: $closureBase; + + $result = $closure(...$parameters); + + if (!($result instanceof $className)) { + return $result; + } + + if ($downContext instanceof CarbonInterface && $result instanceof CarbonInterface) { + if ($context !== $result) { + $downContext = $downContext->copy(); + } + + return $downContext + ->setTimezone($result->getTimezone()) + ->modify($result->format('Y-m-d H:i:s.u')) + ->settings($result->getSettings()); + } + + if ($downContext instanceof CarbonInterval && $result instanceof CarbonInterval) { + if ($context !== $result) { + $downContext = $downContext->copy(); + } + + $downContext->copyProperties($result); + self::copyStep($downContext, $result); + self::copyNegativeUnits($downContext, $result); + + return $downContext->settings($result->getSettings()); + } + + if ($downContext instanceof CarbonPeriod && $result instanceof CarbonPeriod) { + if ($context !== $result) { + $downContext = $downContext->copy(); + } + + return $downContext + ->setDates($result->getStartDate(), $result->getEndDate()) + ->setRecurrences($result->getRecurrences()) + ->setOptions($result->getOptions()) + ->settings($result->getSettings()); + } + + return $result; + }); + } + } + + private static function getAnonymousClassCodeForTrait(string $trait) + { + return 'return new class() extends '.static::class.' {use '.$trait.';};'; + } + + private static function getMixableMethods(self $context): Generator + { + foreach (get_class_methods($context) as $name) { + if (method_exists(static::class, $name)) { + continue; + } + + yield $name; + } + } + + /** + * Stack a EDD\Vendor\Carbon context from inside calls of self::this() and execute a given action. + * + * @param static|null $context + * @param callable $callable + * + * @throws Throwable + * + * @return mixed + */ + protected static function bindMacroContext($context, callable $callable) + { + static::$macroContextStack[] = $context; + + try { + return $callable(); + } finally { + array_pop(static::$macroContextStack); + } + } + + /** + * Return the current context from inside a macro callee or a null if static. + * + * @return static|null + */ + protected static function context() + { + return end(static::$macroContextStack) ?: null; + } + + /** + * Return the current context from inside a macro callee or a new one if static. + * + * @return static + */ + protected static function this() + { + return end(static::$macroContextStack) ?: new static(); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Modifiers.php b/libraries/Carbon/src/Carbon/Traits/Modifiers.php new file mode 100644 index 00000000000..23cd5a88c28 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Modifiers.php @@ -0,0 +1,472 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\CarbonInterface; +use ReturnTypeWillChange; + +/** + * Trait Modifiers. + * + * Returns dates relative to current date using modifier short-hand. + */ +trait Modifiers +{ + /** + * Midday/noon hour. + * + * @var int + */ + protected static $midDayAt = 12; + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt() + { + return static::$midDayAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour) + { + static::$midDayAt = $hour; + } + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay() + { + return $this->setTime(static::$midDayAt, 0, 0, 0); + } + + /** + * Modify to the next occurrence of a given modifier such as a day of + * the week. If no modifier is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static|false + */ + public function next($modifier = null) + { + if ($modifier === null) { + $modifier = $this->dayOfWeek; + } + + return $this->change( + 'next '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + ); + } + + /** + * Go forward or backward to the next week- or weekend-day. + * + * @param bool $weekday + * @param bool $forward + * + * @return static + */ + private function nextOrPreviousDay($weekday = true, $forward = true) + { + /** @var CarbonInterface $date */ + $date = $this; + $step = $forward ? 1 : -1; + + do { + $date = $date->addDays($step); + } while ($weekday ? $date->isWeekend() : $date->isWeekday()); + + return $date; + } + + /** + * Go forward to the next weekday. + * + * @return static + */ + public function nextWeekday() + { + return $this->nextOrPreviousDay(); + } + + /** + * Go backward to the previous weekday. + * + * @return static + */ + public function previousWeekday() + { + return $this->nextOrPreviousDay(true, false); + } + + /** + * Go forward to the next weekend day. + * + * @return static + */ + public function nextWeekendDay() + { + return $this->nextOrPreviousDay(false); + } + + /** + * Go backward to the previous weekend day. + * + * @return static + */ + public function previousWeekendDay() + { + return $this->nextOrPreviousDay(false, false); + } + + /** + * Modify to the previous occurrence of a given modifier such as a day of + * the week. If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static|false + */ + public function previous($modifier = null) + { + if ($modifier === null) { + $modifier = $this->dayOfWeek; + } + + return $this->change( + 'last '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + ); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null) + { + $date = $this->startOfDay(); + + if ($dayOfWeek === null) { + return $date->day(1); + } + + return $date->modify('first '.static::$days[$dayOfWeek].' of '.$date->rawFormat('F').' '.$date->year); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null) + { + $date = $this->startOfDay(); + + if ($dayOfWeek === null) { + return $date->day($date->daysInMonth); + } + + return $date->modify('last '.static::$days[$dayOfWeek].' of '.$date->rawFormat('F').' '.$date->year); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->firstOfMonth(); + $check = $date->rawFormat('Y-m'); + $date = $date->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $date->rawFormat('Y-m') === $check ? $this->modify((string) $date) : false; + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER - 2, 1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER, 1)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->day(1)->month($this->quarter * static::MONTHS_PER_QUARTER); + $lastMonth = $date->month; + $year = $date->year; + $date = $date->firstOfQuarter()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return ($lastMonth < $date->month || $year !== $date->year) ? false : $this->modify((string) $date); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null) + { + return $this->month(1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null) + { + return $this->month(static::MONTHS_PER_YEAR)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->firstOfYear()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $this->year === $date->year ? $this->modify((string) $date) : false; + } + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance + * (second-precision). + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|null $date + * + * @return static + */ + public function average($date = null) + { + return $this->addRealMicroseconds((int) ($this->diffInRealMicroseconds($this->resolveCarbon($date), false) / 2)); + } + + /** + * Get the closest date from the instance (second-precision). + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2) + { + return $this->diffInRealMicroseconds($date1) < $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + } + + /** + * Get the farthest date from the instance (second-precision). + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2) + { + return $this->diffInRealMicroseconds($date1) > $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function min($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->lt($date) ? $this : $date; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null) + { + return $this->min($date); + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function max($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->gt($date) ? $this : $date; + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \EDD\Vendor\Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null) + { + return $this->max($date); + } + + /** + * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. + * + * @see https://php.net/manual/en/datetime.modify.php + * + * @return static|false + */ + #[ReturnTypeWillChange] + public function modify($modify) + { + return parent::modify((string) $modify); + } + + /** + * Similar to native modify() method of DateTime but can handle more grammars. + * + * @example + * ``` + * echo Carbon::now()->change('next 2pm'); + * ``` + * + * @link https://php.net/manual/en/datetime.modify.php + * + * @param string $modifier + * + * @return static|false + */ + public function change($modifier) + { + return $this->modify(preg_replace_callback('/^(next|previous|last)\s+(\d{1,2}(h|am|pm|:\d{1,2}(:\d{1,2})?))$/i', function ($match) { + $match[2] = str_replace('h', ':00', $match[2]); + $test = $this->avoidMutation()->modify($match[2]); + $method = $match[1] === 'next' ? 'lt' : 'gt'; + $match[1] = $test->$method($this) ? $match[1].' day' : 'today'; + + return $match[1].' '.$match[2]; + }, strtr(trim($modifier), [ + ' at ' => ' ', + 'just now' => 'now', + 'after tomorrow' => 'tomorrow +1 day', + 'before yesterday' => 'yesterday -1 day', + ]))); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Mutability.php b/libraries/Carbon/src/Carbon/Traits/Mutability.php new file mode 100644 index 00000000000..973f0a3cb97 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Mutability.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\Carbon; +use EDD\Vendor\Carbon\CarbonImmutable; + +/** + * Trait Mutability. + * + * Utils to know if the current object is mutable or immutable and convert it. + */ +trait Mutability +{ + use Cast; + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable() + { + return false; + } + + /** + * Returns true if the current class/instance is immutable. + * + * @return bool + */ + public static function isImmutable() + { + return !static::isMutable(); + } + + /** + * Return a mutable copy of the instance. + * + * @return EDD\Vendor\Carbon + */ + public function toMutable() + { + /** @var EDD\Vendor\Carbon $date */ + $date = $this->cast(Carbon::class); + + return $date; + } + + /** + * Return a immutable copy of the instance. + * + * @return CarbonImmutable + */ + public function toImmutable() + { + /** @var CarbonImmutable $date */ + $date = $this->cast(CarbonImmutable::class); + + return $date; + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/ObjectInitialisation.php b/libraries/Carbon/src/Carbon/Traits/ObjectInitialisation.php new file mode 100644 index 00000000000..2a9aed1fe16 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/ObjectInitialisation.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +trait ObjectInitialisation +{ + /** + * True when parent::__construct has been called. + * + * @var string + */ + protected $constructedObjectId; +} diff --git a/libraries/Carbon/src/Carbon/Traits/Options.php b/libraries/Carbon/src/Carbon/Traits/Options.php new file mode 100644 index 00000000000..2d12ef52dc0 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Options.php @@ -0,0 +1,471 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\CarbonInterface; +use DateTimeInterface; +use Throwable; + +/** + * Trait Options. + * + * Embed base methods to change settings of EDD\Vendor\Carbon classes. + * + * Depends on the following methods: + * + * @method static shiftTimezone($timezone) Set the timezone + */ +trait Options +{ + use Localization; + + /** + * Customizable PHP_INT_SIZE override. + * + * @var int + */ + public static $PHPIntSize = PHP_INT_SIZE; + + /** + * First day of week. + * + * @var int|string + */ + protected static $weekStartsAt = CarbonInterface::MONDAY; + + /** + * Last day of week. + * + * @var int|string + */ + protected static $weekEndsAt = CarbonInterface::SUNDAY; + + /** + * Days of weekend. + * + * @var array + */ + protected static $weekendDays = [ + CarbonInterface::SATURDAY, + CarbonInterface::SUNDAY, + ]; + + /** + * Format regex patterns. + * + * @var array + */ + protected static $regexFormats = [ + 'd' => '(3[01]|[12][0-9]|0[1-9])', + 'D' => '(Sun|Mon|Tue|Wed|Thu|Fri|Sat)', + 'j' => '([123][0-9]|[1-9])', + 'l' => '([a-zA-Z]{2,})', + 'N' => '([1-7])', + 'S' => '(st|nd|rd|th)', + 'w' => '([0-6])', + 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])', + 'W' => '(5[012]|[1-4][0-9]|0?[1-9])', + 'F' => '([a-zA-Z]{2,})', + 'm' => '(1[012]|0[1-9])', + 'M' => '([a-zA-Z]{3})', + 'n' => '(1[012]|[1-9])', + 't' => '(2[89]|3[01])', + 'L' => '(0|1)', + 'o' => '([1-9][0-9]{0,4})', + 'Y' => '([1-9]?[0-9]{4})', + 'y' => '([0-9]{2})', + 'a' => '(am|pm)', + 'A' => '(AM|PM)', + 'B' => '([0-9]{3})', + 'g' => '(1[012]|[1-9])', + 'G' => '(2[0-3]|1?[0-9])', + 'h' => '(1[012]|0[1-9])', + 'H' => '(2[0-3]|[01][0-9])', + 'i' => '([0-5][0-9])', + 's' => '([0-5][0-9])', + 'u' => '([0-9]{1,6})', + 'v' => '([0-9]{1,3})', + 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\\/[a-zA-Z]*)', + 'I' => '(0|1)', + 'O' => '([+-](1[0123]|0[0-9])[0134][05])', + 'P' => '([+-](1[0123]|0[0-9]):[0134][05])', + 'p' => '(Z|[+-](1[0123]|0[0-9]):[0134][05])', + 'T' => '([a-zA-Z]{1,5})', + 'Z' => '(-?[1-5]?[0-9]{1,4})', + 'U' => '([0-9]*)', + + // The formats below are combinations of the above formats. + 'c' => '(([1-9]?[0-9]{4})-(1[012]|0[1-9])-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[+-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP + 'r' => '(([a-zA-Z]{3}), ([123][0-9]|0[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [+-](1[012]|0[0-9])([0134][05]))', // D, d M Y H:i:s O + ]; + + /** + * Format modifiers (such as available in createFromFormat) regex patterns. + * + * @var array + */ + protected static $regexFormatModifiers = [ + '*' => '.+', + ' ' => '[ ]', + '#' => '[;:\\/.,()-]', + '?' => '([^a]|[a])', + '!' => '', + '|' => '', + '+' => '', + ]; + + /** + * Indicates if months should be calculated with overflow. + * Global setting. + * + * @var bool + */ + protected static $monthsOverflow = true; + + /** + * Indicates if years should be calculated with overflow. + * Global setting. + * + * @var bool + */ + protected static $yearsOverflow = true; + + /** + * Indicates if the strict mode is in use. + * Global setting. + * + * @var bool + */ + protected static $strictModeEnabled = true; + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected static $formatFunction; + + /** + * Function to call instead of createFromFormat. + * + * @var string|callable|null + */ + protected static $createFromFormatFunction; + + /** + * Function to call instead of parse. + * + * @var string|callable|null + */ + protected static $parseFunction; + + /** + * Indicates if months should be calculated with overflow. + * Specific setting. + * + * @var bool|null + */ + protected $localMonthsOverflow; + + /** + * Indicates if years should be calculated with overflow. + * Specific setting. + * + * @var bool|null + */ + protected $localYearsOverflow; + + /** + * Indicates if the strict mode is in use. + * Specific setting. + * + * @var bool|null + */ + protected $localStrictModeEnabled; + + /** + * Options for diffForHumans and forHumans methods. + * + * @var bool|null + */ + protected $localHumanDiffOptions; + + /** + * Format to use on string cast. + * + * @var string|null + */ + protected $localToStringFormat; + + /** + * Format to use on JSON serialization. + * + * @var string|null + */ + protected $localSerializer; + + /** + * Instance-specific macros. + * + * @var array|null + */ + protected $localMacros; + + /** + * Instance-specific generic macros. + * + * @var array|null + */ + protected $localGenericMacros; + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected $localFormatFunction; + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode($strictModeEnabled = true) + { + static::$strictModeEnabled = $strictModeEnabled; + } + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled() + { + return static::$strictModeEnabled; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true) + { + static::$monthsOverflow = $monthsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow() + { + static::$monthsOverflow = true; + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths() + { + return static::$monthsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true) + { + static::$yearsOverflow = $yearsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow() + { + static::$yearsOverflow = true; + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears() + { + return static::$yearsOverflow; + } + + /** + * Set specific options. + * - strictMode: true|false|null + * - monthOverflow: true|false|null + * - yearOverflow: true|false|null + * - humanDiffOptions: int|null + * - toStringFormat: string|Closure|null + * - toJsonFormat: string|Closure|null + * - locale: string|null + * - timezone: \DateTimeZone|string|int|null + * - macros: array|null + * - genericMacros: array|null + * + * @param array $settings + * + * @return $this|static + */ + public function settings(array $settings) + { + $this->localStrictModeEnabled = $settings['strictMode'] ?? null; + $this->localMonthsOverflow = $settings['monthOverflow'] ?? null; + $this->localYearsOverflow = $settings['yearOverflow'] ?? null; + $this->localHumanDiffOptions = $settings['humanDiffOptions'] ?? null; + $this->localToStringFormat = $settings['toStringFormat'] ?? null; + $this->localSerializer = $settings['toJsonFormat'] ?? null; + $this->localMacros = $settings['macros'] ?? null; + $this->localGenericMacros = $settings['genericMacros'] ?? null; + $this->localFormatFunction = $settings['formatFunction'] ?? null; + + if (isset($settings['locale'])) { + $locales = $settings['locale']; + + if (!\is_array($locales)) { + $locales = [$locales]; + } + + $this->locale(...$locales); + } + + if (isset($settings['innerTimezone'])) { + return $this->setTimezone($settings['innerTimezone']); + } + + if (isset($settings['timezone'])) { + return $this->shiftTimezone($settings['timezone']); + } + + return $this; + } + + /** + * Returns current local settings. + * + * @return array + */ + public function getSettings() + { + $settings = []; + $map = [ + 'localStrictModeEnabled' => 'strictMode', + 'localMonthsOverflow' => 'monthOverflow', + 'localYearsOverflow' => 'yearOverflow', + 'localHumanDiffOptions' => 'humanDiffOptions', + 'localToStringFormat' => 'toStringFormat', + 'localSerializer' => 'toJsonFormat', + 'localMacros' => 'macros', + 'localGenericMacros' => 'genericMacros', + 'locale' => 'locale', + 'tzName' => 'timezone', + 'localFormatFunction' => 'formatFunction', + ]; + + foreach ($map as $property => $key) { + $value = $this->$property ?? null; + + if ($value !== null && ($key !== 'locale' || $value !== 'en' || $this->localTranslator)) { + $settings[$key] = $value; + } + } + + return $settings; + } + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + $infos = array_filter(get_object_vars($this), static function ($var) { + return $var; + }); + + foreach (['dumpProperties', 'constructedObjectId', 'constructed'] as $property) { + if (isset($infos[$property])) { + unset($infos[$property]); + } + } + + $this->addExtraDebugInfos($infos); + + return $infos; + } + + protected function addExtraDebugInfos(&$infos): void + { + if ($this instanceof DateTimeInterface) { + try { + if (!isset($infos['date'])) { + $infos['date'] = $this->format(CarbonInterface::MOCK_DATETIME_FORMAT); + } + + if (!isset($infos['timezone'])) { + $infos['timezone'] = $this->tzName; + } + } catch (Throwable $exception) { + // noop + } + } + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Rounding.php b/libraries/Carbon/src/Carbon/Traits/Rounding.php new file mode 100644 index 00000000000..a34fcd7dbb4 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Rounding.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\Exceptions\UnknownUnitException; + +/** + * Trait Rounding. + * + * Round, ceil, floor units. + * + * Depends on the following methods: + * + * @method static copy() + * @method static startOfWeek(int $weekStartsAt = null) + */ +trait Rounding +{ + use IntervalRounding; + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int $precision + * @param string $function + * + * @return CarbonInterface + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $metaUnits = [ + // @call roundUnit + 'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'], + // @call roundUnit + 'century' => [static::YEARS_PER_CENTURY, 'year'], + // @call roundUnit + 'decade' => [static::YEARS_PER_DECADE, 'year'], + // @call roundUnit + 'quarter' => [static::MONTHS_PER_QUARTER, 'month'], + // @call roundUnit + 'millisecond' => [1000, 'microsecond'], + ]; + $normalizedUnit = static::singularUnit($unit); + $ranges = array_merge(static::getRangesByUnit($this->daysInMonth), [ + // @call roundUnit + 'microsecond' => [0, 999999], + ]); + $factor = 1; + + if ($normalizedUnit === 'week') { + $normalizedUnit = 'day'; + $precision *= static::DAYS_PER_WEEK; + } + + if (isset($metaUnits[$normalizedUnit])) { + [$factor, $normalizedUnit] = $metaUnits[$normalizedUnit]; + } + + $precision *= $factor; + + if (!isset($ranges[$normalizedUnit])) { + throw new UnknownUnitException($unit); + } + + $found = false; + $fraction = 0; + $arguments = null; + $initialValue = null; + $factor = $this->year < 0 ? -1 : 1; + $changes = []; + $minimumInc = null; + + foreach ($ranges as $unit => [$minimum, $maximum]) { + if ($normalizedUnit === $unit) { + $arguments = [$this->$unit, $minimum]; + $initialValue = $this->$unit; + $fraction = $precision - floor($precision); + $found = true; + + continue; + } + + if ($found) { + $delta = $maximum + 1 - $minimum; + $factor /= $delta; + $fraction *= $delta; + $inc = ($this->$unit - $minimum) * $factor; + + if ($inc !== 0.0) { + $minimumInc = $minimumInc ?? ($arguments[0] / pow(2, 52)); + + // If value is still the same when adding a non-zero increment/decrement, + // it means precision got lost in the addition + if (abs($inc) < $minimumInc) { + $inc = $minimumInc * ($inc < 0 ? -1 : 1); + } + + // If greater than $precision, assume precision loss caused an overflow + if ($function !== 'floor' || abs($arguments[0] + $inc - $initialValue) >= $precision) { + $arguments[0] += $inc; + } + } + + $changes[$unit] = round( + $minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0) + ); + + // Cannot use modulo as it lose double precision + while ($changes[$unit] >= $delta) { + $changes[$unit] -= $delta; + } + + $fraction -= floor($fraction); + } + } + + [$value, $minimum] = $arguments; + $normalizedValue = floor($function(($value - $minimum) / $precision) * $precision + $minimum); + + /** @var CarbonInterface $result */ + $result = $this; + + foreach ($changes as $unit => $value) { + $result = $result->$unit($value); + } + + return $result->$normalizedUnit($normalizedValue); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return CarbonInterface + */ + public function round($precision = 1, $function = 'round') + { + return $this->roundWith($precision, $function); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function floor($precision = 1) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function ceil($precision = 1) + { + return $this->round($precision, 'ceil'); + } + + /** + * Round the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function roundWeek($weekStartsAt = null) + { + return $this->closest( + $this->avoidMutation()->floorWeek($weekStartsAt), + $this->avoidMutation()->ceilWeek($weekStartsAt) + ); + } + + /** + * Truncate the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function floorWeek($weekStartsAt = null) + { + return $this->startOfWeek($weekStartsAt); + } + + /** + * Ceil the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function ceilWeek($weekStartsAt = null) + { + if ($this->isMutable()) { + $startOfWeek = $this->avoidMutation()->startOfWeek($weekStartsAt); + + return $startOfWeek != $this ? + $this->startOfWeek($weekStartsAt)->addWeek() : + $this; + } + + $startOfWeek = $this->startOfWeek($weekStartsAt); + + return $startOfWeek != $this ? + $startOfWeek->addWeek() : + $this->avoidMutation(); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Serialization.php b/libraries/Carbon/src/Carbon/Traits/Serialization.php new file mode 100644 index 00000000000..d8e5076411a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Serialization.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\Exceptions\InvalidFormatException; +use ReturnTypeWillChange; +use Throwable; + +/** + * Trait Serialization. + * + * Serialization and JSON stuff. + * + * Depends on the following properties: + * + * @property int $year + * @property int $month + * @property int $daysInMonth + * @property int $quarter + * + * Depends on the following methods: + * + * @method string|static locale(string $locale = null, string ...$fallbackLocales) + * @method string toJSON() + */ +trait Serialization +{ + use ObjectInitialisation; + + /** + * The custom EDD\Vendor\Carbon JSON serializer. + * + * @var callable|null + */ + protected static $serializer; + + /** + * List of key to use for dump/serialization. + * + * @var string[] + */ + protected $dumpProperties = ['date', 'timezone_type', 'timezone']; + + /** + * Locale to dump comes here before serialization. + * + * @var string|null + */ + protected $dumpLocale; + + /** + * Embed date properties to dump in a dedicated variables so it won't overlap native + * DateTime ones. + * + * @var array|null + */ + protected $dumpDateProperties; + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize() + { + return serialize($this); + } + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws InvalidFormatException + * + * @return static + */ + public static function fromSerialized($value) + { + $instance = @unserialize((string) $value); + + if (!$instance instanceof static) { + throw new InvalidFormatException("Invalid serialized value: $value"); + } + + return $instance; + } + + /** + * The __set_state handler. + * + * @param string|array $dump + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump) + { + if (\is_string($dump)) { + return static::parse($dump); + } + + /** @var \DateTimeInterface $date */ + $date = get_parent_class(static::class) && method_exists(parent::class, '__set_state') + ? parent::__set_state((array) $dump) + : (object) $dump; + + return static::instance($date); + } + + /** + * Returns the list of properties to dump on serialize() called on. + * + * Only used by PHP < 7.4. + * + * @return array + */ + public function __sleep() + { + $properties = $this->getSleepProperties(); + + if ($this->localTranslator ?? null) { + $properties[] = 'dumpLocale'; + $this->dumpLocale = $this->locale ?? null; + } + + return $properties; + } + + /** + * Returns the values to dump on serialize() called on. + * + * Only used by PHP >= 7.4. + * + * @return array + */ + public function __serialize(): array + { + // @codeCoverageIgnoreStart + if (isset($this->timezone_type, $this->timezone, $this->date)) { + return [ + 'date' => $this->date ?? null, + 'timezone_type' => $this->timezone_type, + 'timezone' => $this->timezone ?? null, + ]; + } + // @codeCoverageIgnoreEnd + + $timezone = $this->getTimezone(); + $export = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone_type' => $timezone->getType(), + 'timezone' => $timezone->getName(), + ]; + + // @codeCoverageIgnoreStart + if (\extension_loaded('msgpack') && isset($this->constructedObjectId)) { + $export['dumpDateProperties'] = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone' => serialize($this->timezone ?? null), + ]; + } + // @codeCoverageIgnoreEnd + + if ($this->localTranslator ?? null) { + $export['dumpLocale'] = $this->locale ?? null; + } + + return $export; + } + + /** + * Set locale if specified on unserialize() called. + * + * Only used by PHP < 7.4. + * + * @return void + */ + #[ReturnTypeWillChange] + public function __wakeup() + { + if (parent::class && method_exists(parent::class, '__wakeup')) { + // @codeCoverageIgnoreStart + try { + parent::__wakeup(); + } catch (Throwable $exception) { + try { + // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. + ['date' => $date, 'timezone' => $timezone] = $this->dumpDateProperties; + parent::__construct($date, unserialize($timezone)); + } catch (Throwable $ignoredException) { + throw $exception; + } + } + // @codeCoverageIgnoreEnd + } + + $this->constructedObjectId = spl_object_hash($this); + + if (isset($this->dumpLocale)) { + $this->locale($this->dumpLocale); + $this->dumpLocale = null; + } + + $this->cleanupDumpProperties(); + } + + /** + * Set locale if specified on unserialize() called. + * + * Only used by PHP >= 7.4. + * + * @return void + */ + public function __unserialize(array $data): void + { + // @codeCoverageIgnoreStart + try { + $this->__construct($data['date'] ?? null, $data['timezone'] ?? null); + } catch (Throwable $exception) { + if (!isset($data['dumpDateProperties']['date'], $data['dumpDateProperties']['timezone'])) { + throw $exception; + } + + try { + // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. + ['date' => $date, 'timezone' => $timezone] = $data['dumpDateProperties']; + $this->__construct($date, unserialize($timezone)); + } catch (Throwable $ignoredException) { + throw $exception; + } + } + // @codeCoverageIgnoreEnd + + if (isset($data['dumpLocale'])) { + $this->locale($data['dumpLocale']); + } + } + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + $serializer = $this->localSerializer ?? static::$serializer; + + if ($serializer) { + return \is_string($serializer) + ? $this->rawFormat($serializer) + : $serializer($this); + } + + return $this->toJSON(); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform EDD\Vendor\Carbon object before the serialization. + * + * JSON serialize all EDD\Vendor\Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback) + { + static::$serializer = $callback; + } + + /** + * Cleanup properties attached to the public scope of DateTime when a dump of the date is requested. + * foreach ($date as $_) {} + * serializer($date) + * var_export($date) + * get_object_vars($date) + */ + public function cleanupDumpProperties() + { + // @codeCoverageIgnoreStart + if (PHP_VERSION < 8.2) { + foreach ($this->dumpProperties as $property) { + if (isset($this->$property)) { + unset($this->$property); + } + } + } + // @codeCoverageIgnoreEnd + + return $this; + } + + private function getSleepProperties(): array + { + $properties = $this->dumpProperties; + + // @codeCoverageIgnoreStart + if (!\extension_loaded('msgpack')) { + return $properties; + } + + if (isset($this->constructedObjectId)) { + $this->dumpDateProperties = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone' => serialize($this->timezone ?? null), + ]; + + $properties[] = 'dumpDateProperties'; + } + + return $properties; + // @codeCoverageIgnoreEnd + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Test.php b/libraries/Carbon/src/Carbon/Traits/Test.php new file mode 100644 index 00000000000..2ba014d486e --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Test.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\CarbonTimeZone; +use Closure; +use DateTimeImmutable; +use DateTimeInterface; +use InvalidArgumentException; +use Throwable; + +trait Test +{ + /////////////////////////////////////////////////////////////////// + ///////////////////////// TESTING AIDS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * A test EDD\Vendor\Carbon instance to be returned when now instances are created. + * + * @var Closure|static|null + */ + protected static $testNow; + + /** + * The timezone to resto to when clearing the time mock. + * + * @var string|null + */ + protected static $testDefaultTimezone; + + /** + * Set a EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock EDD\Vendor\Carbon instance + */ + public static function setTestNow($testNow = null) + { + static::$testNow = $testNow instanceof self || $testNow instanceof Closure + ? $testNow + : static::make($testNow); + } + + /** + * Set a EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock EDD\Vendor\Carbon instance + */ + public static function setTestNowAndTimezone($testNow = null, $tz = null) + { + if ($testNow) { + self::$testDefaultTimezone = self::$testDefaultTimezone ?? date_default_timezone_get(); + } + + $useDateInstanceTimezone = $testNow instanceof DateTimeInterface; + + if ($useDateInstanceTimezone) { + self::setDefaultTimezone($testNow->getTimezone()->getName(), $testNow); + } + + static::setTestNow($testNow); + + if (!$useDateInstanceTimezone) { + $now = static::getMockedTestNow(\func_num_args() === 1 ? null : $tz); + $tzName = $now ? $now->tzName : null; + self::setDefaultTimezone($tzName ?? self::$testDefaultTimezone ?? 'UTC', $now); + } + + if (!$testNow) { + self::$testDefaultTimezone = null; + } + } + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @template T + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock EDD\Vendor\Carbon instance + * @param Closure(): T $callback + * + * @return T + */ + public static function withTestNow($testNow, $callback) + { + static::setTestNow($testNow); + + try { + $result = $callback(); + } finally { + static::setTestNow(); + } + + return $result; + } + + /** + * Get the EDD\Vendor\Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|static the current instance used for testing + */ + public static function getTestNow() + { + return static::$testNow; + } + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow() + { + return static::getTestNow() !== null; + } + + /** + * Get the mocked date passed in setTestNow() and if it's a Closure, execute it. + * + * @param string|\DateTimeZone $tz + * + * @return \EDD\Vendor\Carbon\CarbonImmutable|\EDD\Vendor\Carbon\Carbon|null + */ + protected static function getMockedTestNow($tz) + { + $testNow = static::getTestNow(); + + if ($testNow instanceof Closure) { + $realNow = new DateTimeImmutable('now'); + $testNow = $testNow(static::parse( + $realNow->format('Y-m-d H:i:s.u'), + $tz ?: $realNow->getTimezone() + )); + } + /* @var \EDD\Vendor\Carbon\CarbonImmutable|\EDD\Vendor\Carbon\Carbon|null $testNow */ + + return $testNow instanceof CarbonInterface + ? $testNow->avoidMutation()->tz($tz) + : $testNow; + } + + protected static function mockConstructorParameters(&$time, $tz) + { + /** @var \EDD\Vendor\Carbon\CarbonImmutable|\EDD\Vendor\Carbon\Carbon $testInstance */ + $testInstance = clone static::getMockedTestNow($tz); + + if (static::hasRelativeKeywords($time)) { + $testInstance = $testInstance->modify($time); + } + + $time = $testInstance instanceof self + ? $testInstance->rawFormat(static::MOCK_DATETIME_FORMAT) + : $testInstance->format(static::MOCK_DATETIME_FORMAT); + } + + private static function setDefaultTimezone($timezone, DateTimeInterface $date = null) + { + $previous = null; + $success = false; + + try { + $success = date_default_timezone_set($timezone); + } catch (Throwable $exception) { + $previous = $exception; + } + + if (!$success) { + $suggestion = @CarbonTimeZone::create($timezone)->toRegionName($date); + + throw new InvalidArgumentException( + "Timezone ID '$timezone' is invalid". + ($suggestion && $suggestion !== $timezone ? ", did you mean '$suggestion'?" : '.')."\n". + "It must be one of the IDs from DateTimeZone::listIdentifiers(),\n". + 'For the record, hours/minutes offset are relevant only for a particular moment, '. + 'but not as a default timezone.', + 0, + $previous + ); + } + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Timestamp.php b/libraries/Carbon/src/Carbon/Traits/Timestamp.php new file mode 100644 index 00000000000..da1a84e0da1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Timestamp.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +/** + * Trait Timestamp. + */ +trait Timestamp +{ + /** + * Create a EDD\Vendor\Carbon instance from a timestamp and set the timezone (use default one if not specified). + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null) + { + return static::createFromTimestampUTC($timestamp)->setTimezone($tz); + } + + /** + * Create a EDD\Vendor\Carbon instance from an timestamp keeping the timezone to UTC. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp) + { + [$integer, $decimal] = self::getIntegerAndDecimalParts($timestamp); + $delta = floor($decimal / static::MICROSECONDS_PER_SECOND); + $integer += $delta; + $decimal -= $delta * static::MICROSECONDS_PER_SECOND; + $decimal = str_pad((string) $decimal, 6, '0', STR_PAD_LEFT); + + return static::rawCreateFromFormat('U u', "$integer $decimal"); + } + + /** + * Create a EDD\Vendor\Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampMsUTC($timestamp) + { + [$milliseconds, $microseconds] = self::getIntegerAndDecimalParts($timestamp, 3); + $sign = $milliseconds < 0 || ($milliseconds === 0.0 && $microseconds < 0) ? -1 : 1; + $milliseconds = abs($milliseconds); + $microseconds = $sign * abs($microseconds) + static::MICROSECONDS_PER_MILLISECOND * ($milliseconds % static::MILLISECONDS_PER_SECOND); + $seconds = $sign * floor($milliseconds / static::MILLISECONDS_PER_SECOND); + $delta = floor($microseconds / static::MICROSECONDS_PER_SECOND); + $seconds += $delta; + $microseconds -= $delta * static::MICROSECONDS_PER_SECOND; + $microseconds = str_pad($microseconds, 6, '0', STR_PAD_LEFT); + + return static::rawCreateFromFormat('U u', "$seconds $microseconds"); + } + + /** + * Create a EDD\Vendor\Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null) + { + return static::createFromTimestampMsUTC($timestamp) + ->setTimezone($tz); + } + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + public function timestamp($unixTimestamp) + { + return $this->setTimestamp($unixTimestamp); + } + + /** + * Returns a timestamp rounded with the given precision (6 by default). + * + * @example getPreciseTimestamp() 1532087464437474 (microsecond maximum precision) + * @example getPreciseTimestamp(6) 1532087464437474 + * @example getPreciseTimestamp(5) 153208746443747 (1/100000 second precision) + * @example getPreciseTimestamp(4) 15320874644375 (1/10000 second precision) + * @example getPreciseTimestamp(3) 1532087464437 (millisecond precision) + * @example getPreciseTimestamp(2) 153208746444 (1/100 second precision) + * @example getPreciseTimestamp(1) 15320874644 (1/10 second precision) + * @example getPreciseTimestamp(0) 1532087464 (second precision) + * @example getPreciseTimestamp(-1) 153208746 (10 second precision) + * @example getPreciseTimestamp(-2) 15320875 (100 second precision) + * + * @param int $precision + * + * @return float + */ + public function getPreciseTimestamp($precision = 6) + { + return round(((float) $this->rawFormat('Uu')) / pow(10, 6 - $precision)); + } + + /** + * Returns the milliseconds timestamps used amongst other by Date javascript objects. + * + * @return float + */ + public function valueOf() + { + return $this->getPreciseTimestamp(3); + } + + /** + * Returns the timestamp with millisecond precision. + * + * @return int + */ + public function getTimestampMs() + { + return (int) $this->getPreciseTimestamp(3); + } + + /** + * @alias getTimestamp + * + * Returns the UNIX timestamp for the current date. + * + * @return int + */ + public function unix() + { + return $this->getTimestamp(); + } + + /** + * Return an array with integer part digits and decimals digits split from one or more positive numbers + * (such as timestamps) as string with the given number of decimals (6 by default). + * + * By splitting integer and decimal, this method obtain a better precision than + * number_format when the input is a string. + * + * @param float|int|string $numbers one or more numbers + * @param int $decimals number of decimals precision (6 by default) + * + * @return array 0-index is integer part, 1-index is decimal part digits + */ + private static function getIntegerAndDecimalParts($numbers, $decimals = 6) + { + if (\is_int($numbers) || \is_float($numbers)) { + $numbers = number_format($numbers, $decimals, '.', ''); + } + + $sign = str_starts_with($numbers, '-') ? -1 : 1; + $integer = 0; + $decimal = 0; + + foreach (preg_split('`[^\d.]+`', $numbers) as $chunk) { + [$integerPart, $decimalPart] = explode('.', "$chunk."); + + $integer += (int) $integerPart; + $decimal += (float) ("0.$decimalPart"); + } + + $overflow = floor($decimal); + $integer += $overflow; + $decimal -= $overflow; + + return [$sign * $integer, $decimal === 0.0 ? 0.0 : $sign * round($decimal * pow(10, $decimals))]; + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/ToStringFormat.php b/libraries/Carbon/src/Carbon/Traits/ToStringFormat.php new file mode 100644 index 00000000000..47edfc0394a --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/ToStringFormat.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use Closure; + +/** + * Trait ToStringFormat. + * + * Handle global format customization for string cast of the object. + */ +trait ToStringFormat +{ + /** + * Format to use for __toString method when type juggling occurs. + * + * @var string|Closure|null + */ + protected static $toStringFormat; + + /** + * Reset the format used to the default when type juggling a EDD\Vendor\Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat() + { + static::setToStringFormat(null); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let EDD\Vendor\Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * + * Set the default format used when type juggling a EDD\Vendor\Carbon instance to a string. + * + * @param string|Closure|null $format + * + * @return void + */ + public static function setToStringFormat($format) + { + static::$toStringFormat = $format; + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Units.php b/libraries/Carbon/src/Carbon/Traits/Units.php new file mode 100644 index 00000000000..61719f092b1 --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Units.php @@ -0,0 +1,412 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +use EDD\Vendor\Carbon\CarbonConverterInterface; +use EDD\Vendor\Carbon\CarbonInterface; +use EDD\Vendor\Carbon\CarbonInterval; +use EDD\Vendor\Carbon\Exceptions\UnitException; +use Closure; +use DateInterval; +use DateMalformedStringException; +use ReturnTypeWillChange; + +/** + * Trait Units. + * + * Add, subtract and set units. + */ +trait Units +{ + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function addRealUnit($unit, $value = 1) + { + switch ($unit) { + // @call addRealUnit + case 'micro': + + // @call addRealUnit + case 'microsecond': + /* @var CarbonInterface $this */ + $diff = $this->microsecond + $value; + $time = $this->getTimestamp(); + $seconds = (int) floor($diff / static::MICROSECONDS_PER_SECOND); + $time += $seconds; + $diff -= $seconds * static::MICROSECONDS_PER_SECOND; + $microtime = str_pad((string) $diff, 6, '0', STR_PAD_LEFT); + $tz = $this->tz; + + return $this->tz('UTC')->modify("@$time.$microtime")->tz($tz); + + // @call addRealUnit + case 'milli': + // @call addRealUnit + case 'millisecond': + return $this->addRealUnit('microsecond', $value * static::MICROSECONDS_PER_MILLISECOND); + + // @call addRealUnit + case 'second': + break; + + // @call addRealUnit + case 'minute': + $value *= static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'hour': + $value *= static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'day': + $value *= static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'week': + $value *= static::DAYS_PER_WEEK * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'month': + $value *= 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'quarter': + $value *= static::MONTHS_PER_QUARTER * 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'year': + $value *= 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'decade': + $value *= static::YEARS_PER_DECADE * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'century': + $value *= static::YEARS_PER_CENTURY * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'millennium': + $value *= static::YEARS_PER_MILLENNIUM * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + default: + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnitException("Invalid unit for real timestamp add/sub: '$unit'"); + } + + return $this; + } + + /* @var CarbonInterface $this */ + return $this->setTimestamp((int) ($this->getTimestamp() + $value)); + } + + public function subRealUnit($unit, $value = 1) + { + return $this->addRealUnit($unit, -$value); + } + + /** + * Returns true if a property can be changed via setter. + * + * @param string $unit + * + * @return bool + */ + public static function isModifiableUnit($unit) + { + static $modifiableUnits = [ + // @call addUnit + 'millennium', + // @call addUnit + 'century', + // @call addUnit + 'decade', + // @call addUnit + 'quarter', + // @call addUnit + 'week', + // @call addUnit + 'weekday', + ]; + + return \in_array($unit, $modifiableUnits, true) || \in_array($unit, static::$units, true); + } + + /** + * Call native PHP DateTime/DateTimeImmutable add() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawAdd(DateInterval $interval) + { + return parent::add($interval); + } + + /** + * Add given units or interval to the current instance. + * + * @example $date->add('hour', 3) + * @example $date->add(15, 'days') + * @example $date->add(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function add($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit, [], true); + } + + if ($unit instanceof CarbonConverterInterface) { + return $this->resolveCarbon($unit->convertDate($this, false)); + } + + if ($unit instanceof Closure) { + return $this->resolveCarbon($unit($this, false)); + } + + if ($unit instanceof DateInterval) { + return parent::add($unit); + } + + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->addUnit($unit, $value, $overflow); + } + + /** + * Add given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function addUnit($unit, $value = 1, $overflow = null) + { + $originalArgs = \func_get_args(); + + $date = $this; + + if (!is_numeric($value) || !(float) $value) { + return $date->isMutable() ? $date : $date->avoidMutation(); + } + + $unit = self::singularUnit($unit); + $metaUnits = [ + 'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'], + 'century' => [static::YEARS_PER_CENTURY, 'year'], + 'decade' => [static::YEARS_PER_DECADE, 'year'], + 'quarter' => [static::MONTHS_PER_QUARTER, 'month'], + ]; + + if (isset($metaUnits[$unit])) { + [$factor, $unit] = $metaUnits[$unit]; + $value *= $factor; + } + + if ($unit === 'weekday') { + $weekendDays = static::getWeekendDays(); + + if ($weekendDays !== [static::SATURDAY, static::SUNDAY]) { + $absoluteValue = abs($value); + $sign = $value / max(1, $absoluteValue); + $weekDaysCount = 7 - min(6, \count(array_unique($weekendDays))); + $weeks = floor($absoluteValue / $weekDaysCount); + + for ($diff = $absoluteValue % $weekDaysCount; $diff; $diff--) { + /** @var static $date */ + $date = $date->addDays($sign); + + while (\in_array($date->dayOfWeek, $weekendDays, true)) { + $date = $date->addDays($sign); + } + } + + $value = $weeks * $sign; + $unit = 'week'; + } + + $timeString = $date->toTimeString(); + } elseif ($canOverflow = (\in_array($unit, [ + 'month', + 'year', + ]) && ($overflow === false || ( + $overflow === null && + ($ucUnit = ucfirst($unit).'s') && + !($this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}()) + )))) { + $day = $date->day; + } + + $value = (int) $value; + + if ($unit === 'milli' || $unit === 'millisecond') { + $unit = 'microsecond'; + $value *= static::MICROSECONDS_PER_MILLISECOND; + } + + // Work-around for bug https://bugs.php.net/bug.php?id=75642 + if ($unit === 'micro' || $unit === 'microsecond') { + $microseconds = $this->micro + $value; + $second = (int) floor($microseconds / static::MICROSECONDS_PER_SECOND); + $microseconds %= static::MICROSECONDS_PER_SECOND; + if ($microseconds < 0) { + $microseconds += static::MICROSECONDS_PER_SECOND; + } + $date = $date->microseconds($microseconds); + $unit = 'second'; + $value = $second; + } + + try { + $date = $date->modify("$value $unit"); + + if (isset($timeString)) { + $date = $date->setTimeFromTimeString($timeString); + } elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) { + $date = $date->modify('last day of previous month'); + } + } catch (DateMalformedStringException $ignoredException) { // @codeCoverageIgnore + $date = null; // @codeCoverageIgnore + } + + if (!$date) { + throw new UnitException('Unable to add unit '.var_export($originalArgs, true)); + } + + return $date; + } + + /** + * Subtract given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subUnit($unit, $value = 1, $overflow = null) + { + return $this->addUnit($unit, -$value, $overflow); + } + + /** + * Call native PHP DateTime/DateTimeImmutable sub() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawSub(DateInterval $interval) + { + return parent::sub($interval); + } + + /** + * Subtract given units or interval to the current instance. + * + * @example $date->sub('hour', 3) + * @example $date->sub(15, 'days') + * @example $date->sub(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit, [], true); + } + + if ($unit instanceof CarbonConverterInterface) { + return $this->resolveCarbon($unit->convertDate($this, true)); + } + + if ($unit instanceof Closure) { + return $this->resolveCarbon($unit($this, true)); + } + + if ($unit instanceof DateInterval) { + return parent::sub($unit); + } + + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->addUnit($unit, -(float) $value, $overflow); + } + + /** + * Subtract given units or interval to the current instance. + * + * @see sub() + * + * @param string|DateInterval $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subtract($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit, [], true); + } + + return $this->sub($unit, $value, $overflow); + } +} diff --git a/libraries/Carbon/src/Carbon/Traits/Week.php b/libraries/Carbon/src/Carbon/Traits/Week.php new file mode 100644 index 00000000000..4e768b3a1cc --- /dev/null +++ b/libraries/Carbon/src/Carbon/Traits/Week.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon\Traits; + +/** + * Trait Week. + * + * week and ISO week number, year and count in year. + * + * Depends on the following properties: + * + * @property int $daysInYear + * @property int $dayOfWeek + * @property int $dayOfYear + * @property int $year + * + * Depends on the following methods: + * + * @method static addWeeks(int $weeks = 1) + * @method static copy() + * @method static dayOfYear(int $dayOfYear) + * @method string getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) + * @method static next(int|string $day = null) + * @method static startOfWeek(int $day = 1) + * @method static subWeeks(int $weeks = 1) + * @method static year(int $year = null) + */ +trait Week +{ + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function isoWeekYear($year = null, $dayOfWeek = null, $dayOfYear = null) + { + return $this->weekYear( + $year, + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null) + { + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + + if ($year !== null) { + $year = (int) round($year); + + if ($this->weekYear(null, $dayOfWeek, $dayOfYear) === $year) { + return $this->avoidMutation(); + } + + $week = $this->week(null, $dayOfWeek, $dayOfYear); + $day = $this->dayOfWeek; + $date = $this->year($year); + switch ($date->weekYear(null, $dayOfWeek, $dayOfYear) - $year) { + case 1: + $date = $date->subWeeks(26); + + break; + case -1: + $date = $date->addWeeks(26); + + break; + } + + $date = $date->addWeeks($week - $date->week(null, $dayOfWeek, $dayOfYear))->startOfWeek($dayOfWeek); + + if ($date->dayOfWeek === $day) { + return $date; + } + + return $date->next($day); + } + + $year = $this->year; + $day = $this->dayOfYear; + $date = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + + if ($date->year === $year && $day < $date->dayOfYear) { + return $year - 1; + } + + $date = $this->avoidMutation()->addYear()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + + if ($date->year === $year && $day >= $date->dayOfYear) { + return $year + 1; + } + + return $year; + } + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null) + { + return $this->weeksInYear( + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function weeksInYear($dayOfWeek = null, $dayOfYear = null) + { + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + $year = $this->year; + $start = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $startDay = $start->dayOfYear; + if ($start->year !== $year) { + $startDay -= $start->daysInYear; + } + $end = $this->avoidMutation()->addYear()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $endDay = $end->dayOfYear; + if ($end->year !== $year) { + $endDay += $this->daysInYear; + } + + return (int) round(($endDay - $startDay) / 7); + } + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function week($week = null, $dayOfWeek = null, $dayOfYear = null) + { + $date = $this; + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + + if ($week !== null) { + return $date->addWeeks(round($week) - $this->week(null, $dayOfWeek, $dayOfYear)); + } + + $start = $date->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $end = $date->avoidMutation()->startOfWeek($dayOfWeek); + if ($start > $end) { + $start = $start->subWeeks(26)->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + } + $week = (int) ($start->diffInDays($end) / 7 + 1); + + return $week > $end->weeksInYear($dayOfWeek, $dayOfYear) ? 1 : $week; + } + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function isoWeek($week = null, $dayOfWeek = null, $dayOfYear = null) + { + return $this->week( + $week, + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } +} diff --git a/libraries/Carbon/src/Carbon/Translator.php b/libraries/Carbon/src/Carbon/Translator.php new file mode 100644 index 00000000000..98f3e11218b --- /dev/null +++ b/libraries/Carbon/src/Carbon/Translator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use ReflectionMethod; +use EDD\Vendor\Symfony\Component\Translation; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; + +$transMethod = new ReflectionMethod( + class_exists(TranslatorInterface::class) + ? TranslatorInterface::class + : Translation\Translator::class, + 'trans' +); + +require $transMethod->hasReturnType() + ? __DIR__.'/../../lazy/Carbon/TranslatorStrongType.php' + : __DIR__.'/../../lazy/Carbon/TranslatorWeakType.php'; + +class Translator extends LazyTranslator +{ + // Proxy dynamically loaded LazyTranslator in a static way +} diff --git a/libraries/Carbon/src/Carbon/TranslatorImmutable.php b/libraries/Carbon/src/Carbon/TranslatorImmutable.php new file mode 100644 index 00000000000..1c070b5ee79 --- /dev/null +++ b/libraries/Carbon/src/Carbon/TranslatorImmutable.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Carbon\Exceptions\ImmutableException; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use EDD\Vendor\Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +class TranslatorImmutable extends Translator +{ + /** @var bool */ + private $constructed = false; + + public function __construct($locale, MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) + { + parent::__construct($locale, $formatter, $cacheDir, $debug); + $this->constructed = true; + } + + /** + * @codeCoverageIgnore + */ + public function setDirectories(array $directories) + { + $this->disallowMutation(__METHOD__); + + return parent::setDirectories($directories); + } + + public function setLocale($locale) + { + $this->disallowMutation(__METHOD__); + + return parent::setLocale($locale); + } + + /** + * @codeCoverageIgnore + */ + public function setMessages($locale, $messages) + { + $this->disallowMutation(__METHOD__); + + return parent::setMessages($locale, $messages); + } + + /** + * @codeCoverageIgnore + */ + public function setTranslations($messages) + { + $this->disallowMutation(__METHOD__); + + return parent::setTranslations($messages); + } + + /** + * @codeCoverageIgnore + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory): void + { + $this->disallowMutation(__METHOD__); + + parent::setConfigCacheFactory($configCacheFactory); + } + + public function resetMessages($locale = null) + { + $this->disallowMutation(__METHOD__); + + return parent::resetMessages($locale); + } + + /** + * @codeCoverageIgnore + */ + public function setFallbackLocales(array $locales) + { + $this->disallowMutation(__METHOD__); + + parent::setFallbackLocales($locales); + } + + private function disallowMutation($method) + { + if ($this->constructed) { + throw new ImmutableException($method.' not allowed on '.static::class); + } + } +} diff --git a/libraries/Carbon/src/Carbon/TranslatorStrongTypeInterface.php b/libraries/Carbon/src/Carbon/TranslatorStrongTypeInterface.php new file mode 100644 index 00000000000..2a3960bd205 --- /dev/null +++ b/libraries/Carbon/src/Carbon/TranslatorStrongTypeInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Carbon; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Mark translator using strong type from symfony/translation >= 6. + */ +interface TranslatorStrongTypeInterface +{ + public function getFromCatalogue(MessageCatalogueInterface $catalogue, string $id, string $domain = 'messages'); +} diff --git a/libraries/Psr/Clock/ClockInterface.php b/libraries/Psr/Clock/ClockInterface.php new file mode 100644 index 00000000000..b1203b12eba --- /dev/null +++ b/libraries/Psr/Clock/ClockInterface.php @@ -0,0 +1,13 @@ + CURL_IPRESOLVE_V4 + ]); + \Stripe\ApiRequestor::setHttpClient($curl); + ``` + + #### ⚠️ Removal of enum values, properties and events that are no longer part of the publicly documented Stripe API + + * Remove the below deprecated values on the enum `BalanceTransaction.Type` + * `obligation_inbound` + * `obligation_payout` + * `obligation_payout_failure` + * `obligation_reversal_outbound` + * Remove the deprecated value `various` on the enum `Climate.Supplier.RemovalPathway` + * Remove deprecated events + * `invoiceitem.updated` + * `order.created` + * `recipient.created` + * `recipient.deleted` + * `recipient.updated` + * `sku.created` + * `sku.deleted` + * `sku.updated` + * Remove the deprecated value `service_tax` on the enum `TaxRate.TaxType` + * Remove support for `id_bank_transfer`, `multibanco`, `netbanking`, `pay_by_bank`, and `upi` on `PaymentMethodConfiguration` + * Remove the legacy field `rendering_options` in `Invoice`. Use `rendering` instead. + +## 13.18.0 - 2024-04-09 +* [#1675](https://github.com/stripe/stripe-php/pull/1675) Update generated code + * Add support for new resources `Entitlements.ActiveEntitlement` and `Entitlements.Feature` + * Add support for `all` and `retrieve` methods on resource `ActiveEntitlement` + * Add support for `all`, `create`, `retrieve`, and `update` methods on resource `Feature` + * Add support for new value `none` on enum `Account.type` + * Add support for `cancel`, `event_name`, and `type` on `Billing.MeterEventAdjustment` + +## 13.17.0 - 2024-04-04 +* [#1670](https://github.com/stripe/stripe-php/pull/1670) Update generated code + * Add support for `subscription_item` on `Discount` + * Add support for `email` and `phone` on `Identity.VerificationReport` + * Add support for `verification_flow` on `Identity.VerificationReport` and `Identity.VerificationSession` + * Add support for new value `verification_flow` on enums `Identity.VerificationReport.type` and `Identity.VerificationSession.type` + * Add support for `provided_details` on `Identity.VerificationSession` + * Change type of `Invoice.discounts` from `nullable(array(expandable(deletable($Discount))))` to `array(expandable(deletable($Discount)))` + * Add support for `zip` on `PaymentMethodConfiguration` + * Add support for `discounts` on `SubscriptionItem` and `Subscription` + * Add support for new value `mobile_phone_reader` on enum `Terminal.Reader.device_type` + +## 13.16.0 - 2024-03-28 +* [#1666](https://github.com/stripe/stripe-php/pull/1666) Update generated code + * Add support for new resources `Billing.MeterEventAdjustment`, `Billing.MeterEvent`, and `Billing.Meter` + * Add support for `all`, `create`, `deactivate`, `reactivate`, `retrieve`, and `update` methods on resource `Meter` + * Add support for `create` method on resources `MeterEventAdjustment` and `MeterEvent` + * Add support for `meter` on `Plan` + +## 13.15.0 - 2024-03-21 +* [#1664](https://github.com/stripe/stripe-php/pull/1664) Update generated code + * Add support for new resources `ConfirmationToken` and `Forwarding.Request` + * Add support for `retrieve` method on resource `ConfirmationToken` + * Add support for `all`, `create`, and `retrieve` methods on resource `Request` + * Add support for new values `forwarding_api_inactive`, `forwarding_api_invalid_parameter`, `forwarding_api_upstream_connection_error`, and `forwarding_api_upstream_connection_timeout` on enum `StripeError.code` + * Add support for `mobilepay` on `PaymentMethod` + * Add support for new value `mobilepay` on enum `PaymentMethod.type` + * Add support for `name` on `Terminal.Configuration` + +## 13.14.0 - 2024-03-14 +* [#1660](https://github.com/stripe/stripe-php/pull/1660) Update generated code + * Add support for new resources `Issuing.PersonalizationDesign` and `Issuing.PhysicalBundle` + * Add support for `all`, `create`, `retrieve`, and `update` methods on resource `PersonalizationDesign` + * Add support for `all` and `retrieve` methods on resource `PhysicalBundle` + * Add support for `personalization_design` on `Issuing.Card` + +## 13.13.0 - 2024-02-29 +* [#1654](https://github.com/stripe/stripe-php/pull/1654) Update generated code + * Change type of `Identity.VerificationSession.type` from `nullable(enum('document'|'id_number'))` to `enum('document'|'id_number')` + * Add resources `Application`, `ConnectCollectionTransfer`, `PlatformTaxFee`, `ReserveTransaction`, `SourceMandateNotification`, and `TaxDeductedAtSource`. These classes have no methods on them, and are used to provide more complete types for PHPDocs. +* [#1657](https://github.com/stripe/stripe-php/pull/1657) Update readme to use addBetaVersion + +## 13.12.0 - 2024-02-22 +* [#1651](https://github.com/stripe/stripe-php/pull/1651) Update generated code + * Add support for `client_reference_id` on `Identity.VerificationReport` and `Identity.VerificationSession` + * Remove support for value `service_tax` from enum `TaxRate.tax_type` +* [#1650](https://github.com/stripe/stripe-php/pull/1650) Add TaxIds API + * Add support for `all`, `create`, `delete`, and `retrieve` methods on resource `TaxId` + * The `instanceUrl` function on `TaxId` now returns the top-level `/v1/tax_ids/{id}` path instead of the `/v1/customers/{customer}/tax_ids/{id}` path. + +## 13.11.0 - 2024-02-15 +* [#1639](https://github.com/stripe/stripe-php/pull/1639) Update generated code + * Add support for `networks` on `Card` + * Add support for new value `financial_connections.account.refreshed_ownership` on enum `Event.type` +* [#1648](https://github.com/stripe/stripe-php/pull/1648) Remove broken methods on CustomerCashBalanceTransaction + * Bugfix: remove support for `CustomerCashBalanceTransaction::all` and `CustomerCashBalanceTransaction::retrieve`. These methods were included in the library unintentionally and never functioned. +* [#1647](https://github.com/stripe/stripe-php/pull/1647) Fix \Stripe\Tax\Settings::update +* [#1646](https://github.com/stripe/stripe-php/pull/1646) Add more specific PHPDoc and Psalm type for RequestOptions arrays on services + +## 13.10.0 - 2024-02-01 +* [#1636](https://github.com/stripe/stripe-php/pull/1636) Update generated code + * Add support for new value `swish` on enum `PaymentLink.payment_method_types[]` + * Add support for `swish` on `PaymentMethod` + * Add support for new value `swish` on enum `PaymentMethod.type` + * Add support for `jurisdiction_level` on `TaxRate` + * Change type of `Terminal.Reader.status` from `string` to `enum('offline'|'online')` +* [#1633](https://github.com/stripe/stripe-php/pull/1633) Update generated code + * Add support for `issuer` on `Invoice` + * Add support for `customer_balance` on `PaymentMethodConfiguration` +* [#1630](https://github.com/stripe/stripe-php/pull/1630) Add paginated requests helper function and use in Search and All + +## 13.9.0 - 2024-01-12 +* [#1629](https://github.com/stripe/stripe-php/pull/1629) Update generated code + * Add support for new resource `CustomerSession` + * Add support for `create` method on resource `CustomerSession` + * Remove support for values `obligation_inbound`, `obligation_payout_failure`, `obligation_payout`, and `obligation_reversal_outbound` from enum `BalanceTransaction.type` + * Add support for `billing_cycle_anchor_config` on `Subscription` + +## 13.8.0 - 2024-01-04 +* [#1627](https://github.com/stripe/stripe-php/pull/1627) Update generated code + * Add support for `retrieve` method on resource `Tax.Registration` + +## 13.7.0 - 2023-12-22 +* [#1621](https://github.com/stripe/stripe-php/pull/1621) Update generated code + * Add support for new resource `FinancialConnections.Transaction` + * Add support for `all` and `retrieve` methods on resource `Transaction` + * Add support for `subscribe` and `unsubscribe` methods on resource `FinancialConnections.Account` + * Add support for new value `financial_connections.account.refreshed_transactions` on enum `Event.type` + * Add support for `subscriptions` and `transaction_refresh` on `FinancialConnections.Account` + * Add support for new value `transactions` on enum `FinancialConnections.Session.prefetch[]` + * Add support for `revolut_pay` on `PaymentMethodConfiguration` + * Remove support for `id_bank_transfer`, `multibanco`, `netbanking`, `pay_by_bank`, and `upi` on `PaymentMethodConfiguration` + * Change type of `Quote.invoice_settings` from `nullable(InvoiceSettingQuoteSetting)` to `InvoiceSettingQuoteSetting` + * Add support for `destination_details` on `Refund` + +## 13.6.0 - 2023-12-07 +* [#1613](https://github.com/stripe/stripe-php/pull/1613) Update generated code + * Add support for new values `customer_tax_location_invalid` and `financial_connections_no_successful_transaction_refresh` on enum `StripeError.code` + * Add support for new values `payment_network_reserve_hold` and `payment_network_reserve_release` on enum `BalanceTransaction.type` + * Remove support for value `various` from enum `Climate.Supplier.removal_pathway` + * Add support for `inactive_message` and `restrictions` on `PaymentLink` +* [#1612](https://github.com/stripe/stripe-php/pull/1612) Report usage of .save and StripeClient + * Reports uses of the deprecated `.save` and of `StripeClient` in `X-Stripe-Client-Telemetry`. (You can disable telemetry via `\Stripe\Stripe::setEnableTelemetry(false);`, see the [README](https://github.com/stripe/stripe-php/blob/master/README.md#telemetry).) + +## 13.5.0 - 2023-11-30 +* [#1611](https://github.com/stripe/stripe-php/pull/1611) Update generated code + * Add support for new resources `Climate.Order`, `Climate.Product`, and `Climate.Supplier` + * Add support for `all`, `cancel`, `create`, `retrieve`, and `update` methods on resource `Order` + * Add support for `all` and `retrieve` methods on resources `Product` and `Supplier` + * Add support for new value `financial_connections_account_inactive` on enum `StripeError.code` + * Add support for new values `climate_order_purchase` and `climate_order_refund` on enum `BalanceTransaction.type` + * Add support for new values `climate.order.canceled`, `climate.order.created`, `climate.order.delayed`, `climate.order.delivered`, `climate.order.product_substituted`, `climate.product.created`, and `climate.product.pricing_updated` on enum `Event.type` + +## 13.4.0 - 2023-11-21 +* [#1608](https://github.com/stripe/stripe-php/pull/1608) Update generated code + Add support for `transferred_to_balance` to `CustomerCashBalanceTransaction` +* [#1605](https://github.com/stripe/stripe-php/pull/1605) Update generated code + * Add support for `network_data` on `Issuing.Transaction` + +## 13.3.0 - 2023-11-09 +* [#1603](https://github.com/stripe/stripe-php/pull/1603) Update generated code + * Add support for new value `terminal_reader_hardware_fault` on enum `StripeError.code` + +## 13.2.1 - 2023-11-06 +* [#1602](https://github.com/stripe/stripe-php/pull/1602) Fix error when "id" is not a string. + +## 13.2.0 - 2023-11-02 +* [#1599](https://github.com/stripe/stripe-php/pull/1599) Update generated code + * Add support for new resource `Tax.Registration` + * Add support for `all`, `create`, and `update` methods on resource `Registration` + * Add support for new value `token_card_network_invalid` on enum `StripeError.code` + * Add support for new value `payment_unreconciled` on enum `BalanceTransaction.type` + * Add support for `revolut_pay` on `PaymentMethod` + * Add support for new value `revolut_pay` on enum `PaymentMethod.type` + +## 13.1.0 - 2023-10-26 +* [#1595](https://github.com/stripe/stripe-php/pull/1595) Update generated code + * Add support for new value `balance_invalid_parameter` on enum `StripeError.code` + +## 13.0.0 - 2023-10-16 +* This release changes the pinned API version to `2023-10-16`. Please read the [API Upgrade Guide](https://stripe.com/docs/upgrades#2023-10-16) and carefully review the API changes before upgrading `stripe-php` package. +* [#1593](https://github.com/stripe/stripe-php/pull/1593) Update generated code + - Added `additional_tos_acceptances` field on `Person` + +## 12.8.0 - 2023-10-16 +* [#1590](https://github.com/stripe/stripe-php/pull/1590) Update generated code + * Add support for new values `issuing_token.created` and `issuing_token.updated` on enum `Event.type` + +## 12.7.0 - 2023-10-11 +* [#1589](https://github.com/stripe/stripe-php/pull/1589) Update generated code + * Add support for `client_secret`, `redirect_on_completion`, `return_url`, and `ui_mode` on `Checkout.Session` + * Add support for `offline` on `Terminal.Configuration` + +## 12.6.0 - 2023-10-05 +* [#1586](https://github.com/stripe/stripe-php/pull/1586) Update generated code + * Add support for new resource `Issuing.Token` + * Add support for `all`, `retrieve`, and `update` methods on resource `Token` + * Add support for `token` on `Issuing.Authorization` and `Issuing.Transaction` +* [#1569](https://github.com/stripe/stripe-php/pull/1569) Fix: Do not bother removing `friendsofphp/php-cs-fixer` + +## 12.5.0 - 2023-09-28 +* [#1582](https://github.com/stripe/stripe-php/pull/1582) Generate Discount, SourceTransaction and use sections in more places +* [#1584](https://github.com/stripe/stripe-php/pull/1584) Update generated code + * Add support for `rendering` on `Invoice` + +## 12.4.0 - 2023-09-21 +* [#1579](https://github.com/stripe/stripe-php/pull/1579) Update generated code + * Add back constant for `invoiceitem.updated` webhook event. This was mistakenly removed in v12.2.0. +* [#1566](https://github.com/stripe/stripe-php/pull/1566) Fix: Remove `squizlabs/php_codesniffer` +* [#1568](https://github.com/stripe/stripe-php/pull/1568) Enhancement: Reference `phpunit.xsd` as installed with `composer` +* [#1565](https://github.com/stripe/stripe-php/pull/1565) Enhancement: Use PHP 8.2 as leading PHP version + +## 12.3.0 - 2023-09-14 +* [#1577](https://github.com/stripe/stripe-php/pull/1577) Update generated code + * Add support for new resource `PaymentMethodConfiguration` + * Add support for `all`, `create`, `retrieve`, and `update` methods on resource `PaymentMethodConfiguration` + * Add support for `payment_method_configuration_details` on `Checkout.Session`, `PaymentIntent`, and `SetupIntent` +* [#1573](https://github.com/stripe/stripe-php/pull/1573) Update generated code + * Add support for `capture`, `create`, `expire`, `increment`, and `reverse` test helper methods on resource `Issuing.Authorization` + * Add support for `create_force_capture`, `create_unlinked_refund`, and `refund` test helper methods on resource `Issuing.Transaction` + * Add support for new value `stripe_tax_inactive` on enum `StripeError.code` + +## 12.2.0 - 2023-09-07 +* [#1571](https://github.com/stripe/stripe-php/pull/1571) Update generated code + * Add support for new resource `PaymentMethodDomain` + * Add support for `all`, `create`, `retrieve`, `update`, and `validate` methods on resource `PaymentMethodDomain` + * Add support for new values `treasury.credit_reversal.created`, `treasury.credit_reversal.posted`, `treasury.debit_reversal.completed`, `treasury.debit_reversal.created`, `treasury.debit_reversal.initial_credit_granted`, `treasury.financial_account.closed`, `treasury.financial_account.created`, `treasury.financial_account.features_status_updated`, `treasury.inbound_transfer.canceled`, `treasury.inbound_transfer.created`, `treasury.inbound_transfer.failed`, `treasury.inbound_transfer.succeeded`, `treasury.outbound_payment.canceled`, `treasury.outbound_payment.created`, `treasury.outbound_payment.expected_arrival_date_updated`, `treasury.outbound_payment.failed`, `treasury.outbound_payment.posted`, `treasury.outbound_payment.returned`, `treasury.outbound_transfer.canceled`, `treasury.outbound_transfer.created`, `treasury.outbound_transfer.expected_arrival_date_updated`, `treasury.outbound_transfer.failed`, `treasury.outbound_transfer.posted`, `treasury.outbound_transfer.returned`, `treasury.received_credit.created`, `treasury.received_credit.failed`, `treasury.received_credit.succeeded`, and `treasury.received_debit.created` on enum `Event.type` + * Remove support for value `invoiceitem.updated` from enum `Event.type` + * Add support for `features` on `Product` + +## 12.1.0 - 2023-08-31 +* [#1560](https://github.com/stripe/stripe-php/pull/1560) Update generated code + * Add support for new resource `AccountSession` + * Add support for `create` method on resource `AccountSession` + * Add support for new values `obligation_inbound`, `obligation_outbound`, `obligation_payout_failure`, `obligation_payout`, `obligation_reversal_inbound`, and `obligation_reversal_outbound` on enum `BalanceTransaction.type` + * Change type of `Event.type` from `string` to `enum` + * Add support for `application` on `PaymentLink` +* [#1562](https://github.com/stripe/stripe-php/pull/1562) Nicer ApiErrorException::__toString() +* [#1558](https://github.com/stripe/stripe-php/pull/1558) Update generated code + * Add support for `payment_method_details` on `Dispute` + * Add support for `prefetch` on `FinancialConnections.Session` + +## 12.0.0 - 2023-08-18 +**⚠️ ACTION REQUIRED: the breaking change in this release likely affects you ⚠️** + +### Version pinning + +In this release, Stripe API Version `2023-08-16` (the latest at time of release) will be sent by default on all requests. This is a significant change with wide ramifications. The API version affects the properties you see on responses, the parameters you are allowed to send on requests, and so on. The previous default was to use your [Stripe account's default API version](https://stripe.com/docs/development/dashboard/request-logs#view-your-default-api-version). + +To successfully upgrade to stripe-php v12, you must either + +1. **(Recommended) Upgrade your integration to be compatible with API Version `2023-08-16`.** + + Please read the API Changelog carefully for each API Version from `2023-08-16` back to your [Stripe account's default API version](https://stripe.com/docs/development/dashboard/request-logs#view-your-default-api-version). Determine if you are using any of the APIs that have changed in a breaking way, and adjust your integration accordingly. Carefully test your changes with Stripe [Test Mode](https://stripe.com/docs/keys#test-live-modes) before deploying them to production. + + You can read the [v12 migration guide](https://github.com/stripe/stripe-php/wiki/Migration-guide-for-v12) for more detailed instructions. +2. **(Alternative option) Specify a version other than `2023-08-16` when initializing `stripe-php`.** + + If you were previously initializing stripe-php without an explicit API Version, you can postpone modifying your integration by specifying a version equal to your [Stripe account's default API version](https://stripe.com/docs/development/dashboard/request-logs#view-your-default-api-version). For example: + + ```diff + // if using StripeClient + - $stripe = new \Stripe\StripeClient('sk_test_xyz'); + + $stripe = new \Stripe\StripeClient([ + + 'api_key' => 'sk_test_xyz', + 'stripe_version' => '2020-08-27', + + ]); + + // if using the global client + Stripe.apiKey = "sk_test_xyz"; + + Stripe::setApiVersion('2020-08-27'); + ``` + + If you were already initializing stripe-php with an explicit API Version, upgrading to v12 will not affect your integration. + + Read the [v12 migration guide](https://github.com/stripe/stripe-php/wiki/Migration-guide-for-v12) for more details. + + Going forward, each major release of this library will be *pinned* by default to the latest Stripe API Version at the time of release. + + That is, instead of upgrading stripe-php and separately upgrading your Stripe API Version through the Stripe Dashboard, whenever you upgrade major versions of stripe-php, you should also upgrade your integration to be compatible with the latest Stripe API version. + +### Other changes +" ⚠️" symbol highlights breaking changes. +* [#1553](https://github.com/stripe/stripe-php/pull/1553)⚠️ Remove deprecated enum value `Invoice.STATUS_DELETE` + +* [#1550](https://github.com/stripe/stripe-php/pull/1550) PHPDoc changes + * Remove support for `alternate_statement_descriptors`, `destination`, and `dispute` on `Charge` + * Remove support for value `charge_refunded` from enum `Dispute.status` + * Remove support for `rendering` on `Invoice` + * Remove support for `attributes`, `caption`, and `deactivate_on` on `Product` + +## 11.0.0 - 2023-08-16 +Please do not use stripe-php v11. It did not correctly apply the [pinning behavior](https://github.com/stripe/stripe-php/blob/master/CHANGELOG.md#version-pinning) and was removed from packagist + +## 10.21.0 - 2023-08-10 +* [#1546](https://github.com/stripe/stripe-php/pull/1546) Update generated code + * Add support for new value `payment_reversal` on enum `BalanceTransaction.type` + * Add support for new value `adjusted_for_overdraft` on enum `CustomerBalanceTransaction.type` + +## 10.20.0 - 2023-08-03 +* [#1539](https://github.com/stripe/stripe-php/pull/1539) Update generated code + * Add support for `subscription_details` on `Invoice` + * Add support for new values `sepa_debit_fingerprint` and `us_bank_account_fingerprint` on enum `Radar.ValueList.item_type` + +## 10.19.0 - 2023-07-27 +* [#1534](https://github.com/stripe/stripe-php/pull/1534) Update generated code + * Improve PHPDoc type for `ApplicationFee.refunds` + * Add support for `deleted` on `Apps.Secret` +* [#1526](https://github.com/stripe/stripe-php/pull/1526) Add constants for payment intent cancellation reasons +* [#1533](https://github.com/stripe/stripe-php/pull/1533) Update generated code + * Add support for new value `service_tax` on enum `TaxRate.tax_type` +* [#1487](https://github.com/stripe/stripe-php/pull/1487) PHPDoc: use union of literals for $method parameter throughout + +## 10.18.0 - 2023-07-20 +* [#1533](https://github.com/stripe/stripe-php/pull/1533) Update generated code + * Add support for new value `service_tax` on enum `TaxRate.tax_type` +* [#1526](https://github.com/stripe/stripe-php/pull/1526) Add constants for payment intent cancellation reasons +* [#1487](https://github.com/stripe/stripe-php/pull/1487) PHPDoc: use union of literals for $method parameter throughout + +## 10.17.0 - 2023-07-13 +* [#1525](https://github.com/stripe/stripe-php/pull/1525) Update generated code + * Add support for new resource `Tax.Settings` + * Add support for `retrieve` and `update` methods on resource `Settings` + * Add support for new value `invalid_tax_location` on enum `StripeError.code` + * Add support for `product` on `Tax.TransactionLineItem` + * Add constant for `tax.settings.updated` webhook event +* [#1520](https://github.com/stripe/stripe-php/pull/1520) Update generated code + * Release specs are identical. + +## 10.16.0 - 2023-06-29 +* [#1517](https://github.com/stripe/stripe-php/pull/1517) Update generated code + * Add support for new value `application_fees_not_allowed` on enum `StripeError.code` + * Add support for `effective_at` on `CreditNote` and `Invoice` + * Add support for `on_behalf_of` on `Mandate` +* [#1514](https://github.com/stripe/stripe-php/pull/1514) Update generated code + * Release specs are identical. +* [#1512](https://github.com/stripe/stripe-php/pull/1512) Update generated code + * Change type of `Checkout.Session.success_url` from `string` to `nullable(string)` + +## 10.15.0 - 2023-06-08 +* [#1506](https://github.com/stripe/stripe-php/pull/1506) Update generated code + * Add support for `preferred_locales` on `Issuing.Cardholder` + +## 10.14.0 - 2023-05-25 +* [#1503](https://github.com/stripe/stripe-php/pull/1503) Update generated code + * Add support for `zip` on `PaymentMethod` + * Add support for new value `zip` on enum `PaymentMethod.type` +* [#1502](https://github.com/stripe/stripe-php/pull/1502) Generate error codes +* [#1501](https://github.com/stripe/stripe-php/pull/1501) Update generated code + +* [#1499](https://github.com/stripe/stripe-php/pull/1499) Update generated code + * Add support for new values `amusement_tax` and `communications_tax` on enum `TaxRate.tax_type` + +## 10.13.0 - 2023-05-11 +* [#1490](https://github.com/stripe/stripe-php/pull/1490) Update generated code + * Add support for `paypal` on `PaymentMethod` + * Add support for `effective_percentage` on `TaxRate` +* [#1488](https://github.com/stripe/stripe-php/pull/1488) Increment PHPStan to strictness level 2 +* [#1483](https://github.com/stripe/stripe-php/pull/1483) Update generated code + +* [#1480](https://github.com/stripe/stripe-php/pull/1480) Update generated code + * Change type of `Identity.VerificationSession.options` from `VerificationSessionOptions` to `nullable(VerificationSessionOptions)` + * Change type of `Identity.VerificationSession.type` from `enum('document'|'id_number')` to `nullable(enum('document'|'id_number'))` +* [#1478](https://github.com/stripe/stripe-php/pull/1478) Update generated code + * Release specs are identical. +* [#1475](https://github.com/stripe/stripe-php/pull/1475) Update generated code + + +## 10.12.1 - 2023-04-04 +* [#1473](https://github.com/stripe/stripe-php/pull/1473) Update generated code + * Add back `deleted` from `Invoice.status`. + +## 10.12.0 - 2023-03-30 +* [#1470](https://github.com/stripe/stripe-php/pull/1470) Update generated code + * Remove support for `create` method on resource `Tax.Transaction` + * This is not a breaking change, as this method was deprecated before the Tax Transactions API was released in favor of the `createFromCalculation` method. + * Remove support for value `deleted` from enum `Invoice.status` + * This is not a breaking change, as the value was never returned or accepted as input. +* [#1468](https://github.com/stripe/stripe-php/pull/1468) Trigger workflow for tags +* [#1467](https://github.com/stripe/stripe-php/pull/1467) Update generated code (new) + * Release specs are identical. + +## 10.11.0 - 2023-03-23 +* [#1458](https://github.com/stripe/stripe-php/pull/1458) Update generated code + * Add support for new resources `Tax.CalculationLineItem`, `Tax.Calculation`, `Tax.TransactionLineItem`, and `Tax.Transaction` + * Add support for `create` and `list_line_items` methods on resource `Calculation` + * Add support for `create_from_calculation`, `create_reversal`, `create`, `list_line_items`, and `retrieve` methods on resource `Transaction` + * Add support for `currency_conversion` on `Checkout.Session` + * Add support for new value `automatic_async` on enum `PaymentIntent.capture_method` + * Add support for new value `link` on enum `PaymentLink.payment_method_types[]` + * Add support for `automatic_payment_methods` on `SetupIntent` + +## 10.10.0 - 2023-03-16 +* [#1457](https://github.com/stripe/stripe-php/pull/1457) API Updates + * Add support for `future_requirements` and `requirements` on `BankAccount` + * Add support for new value `automatic_async` on enum `PaymentIntent.capture_method` + * Add support for new value `cashapp` on enum `PaymentLink.payment_method_types[]` + * Add support for `cashapp` on `PaymentMethod` + * Add support for new value `cashapp` on enum `PaymentMethod.type` +* [#1454](https://github.com/stripe/stripe-php/pull/1454) Update generated code (new) + * Add support for new value `cashapp` on enum `PaymentLink.payment_method_types[]` + * Add support for `cashapp` on `PaymentMethod` + * Add support for new value `cashapp` on enum `PaymentMethod.type` + +## 10.9.1 - 2023-03-14 +* [#1453](https://github.com/stripe/stripe-php/pull/1453) Restore StripeClient.getService + +## 10.9.0 - 2023-03-09 +* [#1450](https://github.com/stripe/stripe-php/pull/1450) API Updates + * Add support for `cancellation_details` on `Subscription` + * Fix return types on custom methods (extends https://github.com/stripe/stripe-php/pull/1446) + +* [#1446](https://github.com/stripe/stripe-php/pull/1446) stripe->customers->retrievePaymentMethod returns the wrong class (type hint) + +## 10.8.0 - 2023-03-02 +* [#1447](https://github.com/stripe/stripe-php/pull/1447) API Updates + * Add support for `reconciliation_status` on `Payout` + * Add support for new value `lease_tax` on enum `TaxRate.tax_type` + +## 10.7.0 - 2023-02-23 +* [#1444](https://github.com/stripe/stripe-php/pull/1444) API Updates + * Add support for new value `igst` on enum `TaxRate.tax_type` + +## 10.6.1 - 2023-02-21 +* [#1443](https://github.com/stripe/stripe-php/pull/1443) Remove init.php from the list of ignored files + +## 10.6.0 - 2023-02-16 +* [#1441](https://github.com/stripe/stripe-php/pull/1441) API Updates + * Add support for `refund_payment` method on resource `Terminal.Reader` + * Add support for `custom_fields` on `Checkout.Session` and `PaymentLink` +* [#1236](https://github.com/stripe/stripe-php/pull/1236) subscription_proration_date not always presented in Invoice +* [#1431](https://github.com/stripe/stripe-php/pull/1431) Fix: Do not use unbounded version constraint for `actions/checkout` +* [#1436](https://github.com/stripe/stripe-php/pull/1436) Enhancement: Enable and configure `visibility_required` fixer +* [#1432](https://github.com/stripe/stripe-php/pull/1432) Enhancement: Update `actions/cache` +* [#1434](https://github.com/stripe/stripe-php/pull/1434) Fix: Remove parentheses +* [#1433](https://github.com/stripe/stripe-php/pull/1433) Enhancement: Run tests on PHP 8.2 +* [#1438](https://github.com/stripe/stripe-php/pull/1438) Update .gitattributes + +## 10.5.0 - 2023-02-02 +* [#1439](https://github.com/stripe/stripe-php/pull/1439) API Updates + * Add support for `resume` method on resource `Subscription` + * Add support for `amount_shipping` and `shipping_cost` on `CreditNote` and `Invoice` + * Add support for `shipping_details` on `Invoice` + * Add support for `invoice_creation` on `PaymentLink` + * Add support for `trial_settings` on `Subscription` + * Add support for new value `paused` on enum `Subscription.status` + +## 10.4.0 - 2023-01-19 +* [#1381](https://github.com/stripe/stripe-php/pull/1381) Add getService methods to StripeClient and AbstractServiceFactory to allow mocking +* [#1424](https://github.com/stripe/stripe-php/pull/1424) API Updates + + * Added `REFUND_CREATED`, `REFUND_UPDATED` event definitions. +* [#1426](https://github.com/stripe/stripe-php/pull/1426) Ignore PHP version for formatting +* [#1425](https://github.com/stripe/stripe-php/pull/1425) Fix Stripe::setAccountId parameter type +* [#1418](https://github.com/stripe/stripe-php/pull/1418) Switch to mb_convert_encoding to fix utf8_encode deprecation warning + +## 10.3.0 - 2022-12-22 +* [#1413](https://github.com/stripe/stripe-php/pull/1413) API Updates + Change `CheckoutSession.cancel_url` to be nullable. + +## 10.2.0 - 2022-12-15 +* [#1411](https://github.com/stripe/stripe-php/pull/1411) API Updates + * Add support for new value `invoice_overpaid` on enum `CustomerBalanceTransaction.type` +* [#1407](https://github.com/stripe/stripe-php/pull/1407) API Updates + + +## 10.1.0 - 2022-12-06 +* [#1405](https://github.com/stripe/stripe-php/pull/1405) API Updates + * Add support for `flow` on `BillingPortal.Session` +* [#1404](https://github.com/stripe/stripe-php/pull/1404) API Updates + * Remove support for resources `Order` and `Sku` + * Remove support for `all`, `cancel`, `create`, `list_line_items`, `reopen`, `retrieve`, `submit`, and `update` methods on resource `Order` + * Remove support for `all`, `create`, `delete`, `retrieve`, and `update` methods on resource `Sku` + * Add support for `custom_text` on `Checkout.Session` and `PaymentLink` + * Add support for `invoice_creation` and `invoice` on `Checkout.Session` + * Remove support for `product` on `LineItem` + * Add support for `latest_charge` on `PaymentIntent` + * Remove support for `charges` on `PaymentIntent` + +## 10.0.0 - 2022-11-16 +* [#1392](https://github.com/stripe/stripe-php/pull/1392) Next major release changes + +Breaking changes that arose during code generation of the library that we postponed for the next major version. For changes to the Stripe products, read more at https://stripe.com/docs/upgrades#2022-11-15. + +"⚠️" symbol highlights breaking changes. + +### Deprecated +* [#1382](https://github.com/stripe/stripe-php/pull/1382) Mark `resource.save` as deprecated. Prefer the static update method that doesn't require retrieval of the resource to update it. +```PHP +// before +$resource = Price::retrieve(self::TEST_RESOURCE_ID); +$resource->metadata['key'] = 'value'; +$resource->save(); + +// after +$resource = Price::update('price_123', [ + 'metadata' => ['key' => 'value'], +]); +``` + +### ⚠️ Removed +- [#1377](https://github.com/stripe/stripe-php/pull/1377) Removed deprecated `Sku` resource and service +- [#1375](https://github.com/stripe/stripe-php/pull/1375) Removed deprecated `Orders` resource and service +- [#1375](https://github.com/stripe/stripe-php/pull/1375) Removed deprecated `Product` field from the `LineItem` +- [#1388](https://github.com/stripe/stripe-php/pull/1388) Removed deprecated `AlipayAccount` resource +- [#1396](https://github.com/stripe/stripe-php/pull/1396) Removed `charges` field on `PaymentIntent` and replace it with `latest_charge`. + + +## 9.9.0 - 2022-11-08 +* [#1394](https://github.com/stripe/stripe-php/pull/1394) API Updates + * Add support for new values `eg_tin`, `ph_tin`, and `tr_tin` on enum `TaxId.type` +* [#1389](https://github.com/stripe/stripe-php/pull/1389) API Updates + * Add support for `on_behalf_of` on `Subscription` +* [#1379](https://github.com/stripe/stripe-php/pull/1379) Do not run Coveralls in PR-s + +## 9.8.0 - 2022-10-20 +* [#1383](https://github.com/stripe/stripe-php/pull/1383) API Updates + * Add support for new values `jp_trn` and `ke_pin` on enum `TaxId.type` +* [#1293](https://github.com/stripe/stripe-php/pull/1293) Install deps in the install step of CI +* [#1291](https://github.com/stripe/stripe-php/pull/1291) Fix: Configure finder for `friendsofphp/php-cs-fixer` + +## 9.7.0 - 2022-10-13 +* [#1376](https://github.com/stripe/stripe-php/pull/1376) API Updates + * Add support for `network_data` on `Issuing.Authorization` +* [#1374](https://github.com/stripe/stripe-php/pull/1374) Add request_log_url on ErrorObject +* [#1370](https://github.com/stripe/stripe-php/pull/1370) API Updates + * Add support for `created` on `Checkout.Session` + +## 9.6.0 - 2022-09-15 +* [#1365](https://github.com/stripe/stripe-php/pull/1365) API Updates + * Add support for `from_invoice` and `latest_revision` on `Invoice` + * Add support for new value `pix` on enum `PaymentLink.payment_method_types[]` + * Add support for `pix` on `PaymentMethod` + * Add support for new value `pix` on enum `PaymentMethod.type` + * Add support for `created` on `Treasury.CreditReversal` and `Treasury.DebitReversal` + +## 9.5.0 - 2022-09-06 +* [#1364](https://github.com/stripe/stripe-php/pull/1364) API Updates + * Add support for new value `terminal_reader_splashscreen` on enum `File.purpose` +* [#1363](https://github.com/stripe/stripe-php/pull/1363) chore: Update PHP tests to handle search methods. + +## 9.4.0 - 2022-08-26 +* [#1362](https://github.com/stripe/stripe-php/pull/1362) API Updates + * Add support for `login_page` on `BillingPortal.Configuration` +* [#1360](https://github.com/stripe/stripe-php/pull/1360) Add test coverage using Coveralls +* [#1361](https://github.com/stripe/stripe-php/pull/1361) fix: Fix type hints for error objects. + * Update `Invoice.last_finalization_error`, `PaymentIntent.last_payment_error`, `SetupAttempt.setup_error` and `SetupIntent.setup_error` type to be `StripeObject`. + * Addresses https://github.com/stripe/stripe-php/issues/1353. The library today does not actually return a `ErrorObject` for these fields, so the type annotation was incorrect. +* [#1356](https://github.com/stripe/stripe-php/pull/1356) Add beta readme.md section + +## 9.3.0 - 2022-08-23 +* [#1355](https://github.com/stripe/stripe-php/pull/1355) API Updates + * Change type of `Treasury.OutboundTransfer.destination_payment_method` from `string` to `string | null` + * Change the return type of `CustomerService.fundCashBalance` test helper from `CustomerBalanceTransaction` to `CustomerCashBalanceTransaction`. + * This would generally be considered a breaking change, but we've worked with all existing users to migrate and are comfortable releasing this as a minor as it is solely a test helper method. This was essentially broken prior to this change. + +## 9.2.0 - 2022-08-19 +* [#1352](https://github.com/stripe/stripe-php/pull/1352) API Updates + * Add support for new resource `CustomerCashBalanceTransaction` + * Add support for `currency` on `PaymentLink` + * Add constant for `customer_cash_balance_transaction.created` webhook event. +* [#1351](https://github.com/stripe/stripe-php/pull/1351) Add a support section to the readme +* [#1304](https://github.com/stripe/stripe-php/pull/1304) Allow passing PSR-3 loggers to setLogger as they are compatible + +## 9.1.0 - 2022-08-11 +* [#1348](https://github.com/stripe/stripe-php/pull/1348) API Updates + * Add support for `payment_method_collection` on `Checkout.Session` and `PaymentLink` + +* [#1346](https://github.com/stripe/stripe-php/pull/1346) API Updates + * Add support for `expires_at` on `Apps.Secret` + +## 9.0.0 - 2022-08-02 + +Breaking changes that arose during code generation of the library that we postponed for the next major version. For changes to the SDK, read more detailed description at https://github.com/stripe/stripe-php/wiki/Migration-guide-for-v9. For changes to the Stripe products, read more at https://stripe.com/docs/upgrades#2022-08-01. + +"⚠️" symbol highlights breaking changes. + +* [#1344](https://github.com/stripe/stripe-php/pull/1344) API Updates +* [#1337](https://github.com/stripe/stripe-php/pull/1337) API Updates +* [#1273](https://github.com/stripe/stripe-php/pull/1273) Add some PHPDoc return types and fixes +* [#1341](https://github.com/stripe/stripe-php/pull/1341) Next major release changes + +### Added +* Add `alternate_statement_descriptors`, `authorization_code`, and `level3` properties to `Charge` resource. +* Add `previewLines` method to `CreditNote` resource. +* Add `transfer_data` property to `Subscription` resource. +* Add `SOURCE_TYPE_FPX` constant to `Transfer` resource. +* Add new error code constants to `ErrorObject`. +* Add support for `shipping_cost` and `shipping_details` on `Checkout.Session` + +### ⚠️ Changed +* Updated certificate bundle ([#1314](https://github.com/stripe/stripe-php/pull/1314)) +* Add `params` parameter to `close` method in `Dispute` resource. + +### ⚠️ Removed +* Remove deprecated `AlipayAccount`, `BitcoinReceiver`, `BitcoinTransaction`, `Recipient`, `RecipientTransfer`, and `ThreeDSecure` resources. +* Remove `CAPABILITY_CARD_PAYMENTS`, `CAPABILITY_LEGACY_PAYMENTS`, `CAPABILITY_PLATFORM_PAYMENTS`, `CAPABILITY_TRANSFERS`, `CAPABILITY_STATUS_ACTIVE`, `CAPABILITY_STATUS_INACTIVE`, and `CAPABILITY_STATUS_PENDING` constants from `Account` resource. Please use up-to-date values from https://stripe.com/docs/connect/account-capabilities. +* Remove `AssociatedObjects` array property from `EphemeralKey` resource. The field was undocumented and unsupported. +* Remove `details` method from `Card` resource. The endpoint was deprecated and no longer exists. +* Remove `recipient` property from `Card` resource. The property was deprecated. +* Remove ability to list `Card` resources for a particular `Recipient`. +* Remove `sources` property from `Card` resource. The property was deprecated. +* Remove `FAILURE_REASON` constant from `Refund` resource. The value was deprecated. +* Remove `Recipient` resource. The resource was deprecated. +* Remove `OrderItem` resource. The resource was deprecated. +* Remove `all` method from `LineItem`. +* Remove `cancel` method from `Transfer` and `TransferService`. This method is deprecated. +* Remove `allTransactions` method from `SourceService` service. Please use `allSourceTransactions` method instead. +* Remove `persons` method from `Account` resource. Please use `allPersons` method instead. +* Remove `sourceTransactions` method from `Source` resource. Please use `allSourceTransactions` method instead. +* Remove `usageRecordSummaries` method from `SubscriptionItem` resource. Please use `allUsageRecordSummaries` method instead. +* Remove `SOURCE_TYPE_ALIPAY_ACCOUNT` and `SOURCE_TYPE_FINANCING` constants from `Transfer` resource. The values were deprecated and are no longer in use. +* Remove deprecated error code constants from `ErrorObject`: `CODE_ACCOUNT_ALREADY_EXISTS`, `CODE_ORDER_CREATION_FAILED`, `CODE_ORDER_REQUIRED_SETTINGS`, `CODE_ORDER_STATUS_INVALID`, `CODE_ORDER_UPSTREAM_TIMEOUT`, and `CODE_UPSTREAM_ORDER_CREATION_FAILED`. +* Remove deprecated event constants from `Webhook`: `ISSUER_FRAUD_RECORD_CREATED`, ` ORDER_PAYMENT_FAILED`, `ORDER_PAYMENT_SUCCEEDED`, `ORDER_UPDATED`, `ORDER_RETURN_CREATED`, `PAYMENT_METHOD_CARD_AUTOMATICALLY_UPDATED`, `PING`, `PROMOTION_CODE_DELETED`, and `TREASURY_RECEIVED_CREDIT_REVERSED`. The events are deprecated and no longer sent by Stripe. + +## 8.12.0 - 2022-07-25 +* [#1332](https://github.com/stripe/stripe-php/pull/1332) API Updates + * Add support for `default_currency` and `invoice_credit_balance` on `Customer` + + +## 8.11.0 - 2022-07-18 +* [#1324](https://github.com/stripe/stripe-php/pull/1324) API Updates + * Add support for new value `blik` on enum `PaymentLink.payment_method_types[]` + * Add support for `blik` on `PaymentMethod` + * Add support for new value `blik` on enum `PaymentMethod.type` + * Add `Invoice.upcomingLines` method. + * Add `SourceService.allSourceTransactions` method. +* [#1322](https://github.com/stripe/stripe-php/pull/1322) API Updates + * Change type of `source_type` on `Transfer` from nullable string to string (comment-only change) + +## 8.10.0 - 2022-07-07 +* [#1319](https://github.com/stripe/stripe-php/pull/1319) API Updates + * Add support for `currency_options` on `Coupon` and `Price` + * Add support for `currency` on `Subscription` +* [#1318](https://github.com/stripe/stripe-php/pull/1318) API Updates + * Add support for new values financial_connections.account.created, financial_connections.account.deactivated, financial_connections.account.disconnected, financial_connections.account.reactivated, and financial_connections.account.refreshed_balance on `Event`. + +## 8.9.0 - 2022-06-29 +* [#1316](https://github.com/stripe/stripe-php/pull/1316) API Updates + * Add support for `deliver_card`, `fail_card`, `return_card`, and `ship_card` test helper methods on resource `Issuing.Card` + * Add support for `subtotal_excluding_tax` on `CreditNote` and `Invoice` + * Add support for `amount_excluding_tax` and `unit_amount_excluding_tax` on `CreditNoteLineItem` and `InvoiceLineItem` + * Add support for `total_excluding_tax` on `Invoice` + * Change type of `PaymentLink.payment_method_types[]` from `literal('card')` to `enum` + * Add support for `promptpay` on `PaymentMethod` + * Add support for new value `promptpay` on enum `PaymentMethod.type` + * Add support for `hosted_regulatory_receipt_url` and `reversal_details` on `Treasury.ReceivedCredit` and `Treasury.ReceivedDebit` + +## 8.8.0 - 2022-06-23 +* [#1302](https://github.com/stripe/stripe-php/pull/1302) API Updates + * Add support for `custom_unit_amount` on `Price` +* [#1301](https://github.com/stripe/stripe-php/pull/1301) API Updates + + Documentation updates. + +## 8.7.0 - 2022-06-17 +* [#1306](https://github.com/stripe/stripe-php/pull/1306) API Updates + * Add support for `fund_cash_balance` test helper method on resource `Customer` + * Add support for `total_excluding_tax` on `CreditNote` + * Add support for `rendering_options` on `Invoice` +* [#1307](https://github.com/stripe/stripe-php/pull/1307) Support updating pre-release versions +* [#1305](https://github.com/stripe/stripe-php/pull/1305) Trigger workflows on beta branches +* [#1302](https://github.com/stripe/stripe-php/pull/1302) API Updates + * Add support for `custom_unit_amount` on `Price` +* [#1301](https://github.com/stripe/stripe-php/pull/1301) API Updates + + Documentation updates. + +## 8.6.0 - 2022-06-08 +* [#1300](https://github.com/stripe/stripe-php/pull/1300) API Updates + * Add support for `attach_to_self` and `flow_directions` on `SetupAttempt` + +## 8.5.0 - 2022-06-01 +* [#1298](https://github.com/stripe/stripe-php/pull/1298) API Updates + * Add support for `radar_options` on `Charge` and `PaymentMethod` + * Add support for new value `simulated_wisepos_e` on enum `Terminal.Reader.device_type` + +## 8.4.0 - 2022-05-26 +* [#1296](https://github.com/stripe/stripe-php/pull/1296) API Updates + * Add support for `persons` method on resource `Account` + * Add support for `balance_transactions` method on resource `Customer` + * Add support for `id_number_secondary_provided` on `Person` +* [#1295](https://github.com/stripe/stripe-php/pull/1295) API Updates + + +## 8.3.0 - 2022-05-23 +* [#1294](https://github.com/stripe/stripe-php/pull/1294) API Updates + * Add support for new resource `Apps.Secret` + * Add support for `affirm` and `link` on `PaymentMethod` + * Add support for new values `affirm` and `link` on enum `PaymentMethod.type` +* [#1289](https://github.com/stripe/stripe-php/pull/1289) fix: Update RequestOptions#redactedApiKey to stop exploding null. + +## 8.2.0 - 2022-05-19 +* [#1286](https://github.com/stripe/stripe-php/pull/1286) API Updates + * Add support for new resources `Treasury.CreditReversal`, `Treasury.DebitReversal`, `Treasury.FinancialAccountFeatures`, `Treasury.FinancialAccount`, `Treasury.FlowDetails`, `Treasury.InboundTransfer`, `Treasury.OutboundPayment`, `Treasury.OutboundTransfer`, `Treasury.ReceivedCredit`, `Treasury.ReceivedDebit`, `Treasury.TransactionEntry`, and `Treasury.Transaction` + * Add support for `retrieve_payment_method` method on resource `Customer` + * Add support for `all` and `list_owners` methods on resource `FinancialConnections.Account` + * Add support for `treasury` on `Issuing.Authorization`, `Issuing.Dispute`, and `Issuing.Transaction` + * Add support for `financial_account` on `Issuing.Card` + * Add support for `client_secret` on `Order` + * Add support for `attach_to_self` and `flow_directions` on `SetupIntent` + +## 8.1.0 - 2022-05-11 +* [#1284](https://github.com/stripe/stripe-php/pull/1284) API Updates + * Add support for `consent_collection`, `customer_creation`, `payment_intent_data`, `shipping_options`, `submit_type`, and `tax_id_collection` on `PaymentLink` + * Add support for `description` on `Subscription` + +## 8.0.0 - 2022-05-09 +* [#1283](https://github.com/stripe/stripe-php/pull/1283) Major version release of v8.0.0. The [migration guide](https://github.com/stripe/stripe-php/wiki/Migration-Guide-for-v8) contains more information. + (⚠️ = breaking changes): + * ⚠️ Replace the legacy `Order` API with the new `Order` API. + * Resource modified: `Order`. + * New methods: `cancel`, `list_line_items`, `reopen`, and `submit` + * Removed methods: `pay` and `return_order` + * Removed resources: `OrderItem` and `OrderReturn` + * Removed references from other resources: `Charge.order` + * ⚠️ Rename `\FinancialConnections\Account.refresh` method to `\FinancialConnections\Account.refresh_account` + * Add support for `amount_discount`, `amount_tax`, and `product` on `LineItem` + +## 7.128.0 - 2022-05-05 +* [#1282](https://github.com/stripe/stripe-php/pull/1282) API Updates + * Add support for `default_price` on `Product` + * Add support for `instructions_email` on `Refund` + + +## 7.127.0 - 2022-05-05 +* [#1281](https://github.com/stripe/stripe-php/pull/1281) API Updates + * Add support for new resources `FinancialConnections.AccountOwner`, `FinancialConnections.AccountOwnership`, `FinancialConnections.Account`, and `FinancialConnections.Session` +* [#1278](https://github.com/stripe/stripe-php/pull/1278) Pin setup-php action version. +* [#1277](https://github.com/stripe/stripe-php/pull/1277) API Updates + * Add support for `registered_address` on `Person` + +## 7.126.0 - 2022-05-03 +* [#1276](https://github.com/stripe/stripe-php/pull/1276) API Updates + * Add support for new resource `CashBalance` + * Change type of `BillingPortal.Configuration.application` from `$Application` to `deletable($Application)` + * Add support for `cash_balance` on `Customer` + * Add support for `application` on `Invoice`, `Quote`, `SubscriptionSchedule`, and `Subscription` + * Add support for new value `eu_oss_vat` on enum `TaxId.type` +* [#1274](https://github.com/stripe/stripe-php/pull/1274) Fix PHPDoc on Discount for nullable properties +* [#1272](https://github.com/stripe/stripe-php/pull/1272) Allow users to pass a custom IPRESOLVE cURL option. + +## 7.125.0 - 2022-04-21 +* [#1270](https://github.com/stripe/stripe-php/pull/1270) API Updates + * Add support for `expire` test helper method on resource `Refund` + +## 7.124.0 - 2022-04-18 +* [#1265](https://github.com/stripe/stripe-php/pull/1265) API Updates + * Add support for new resources `FundingInstructions` and `Terminal.Configuration` + * Add support for `create_funding_instructions` method on resource `Customer` + * Add support for `amount_details` on `PaymentIntent` + * Add support for `customer_balance` on `PaymentMethod` + * Add support for new value `customer_balance` on enum `PaymentMethod.type` + * Add support for `configuration_overrides` on `Terminal.Location` + + +## 7.123.0 - 2022-04-13 +* [#1263](https://github.com/stripe/stripe-php/pull/1263) API Updates + * Add support for `increment_authorization` method on resource `PaymentIntent` +* [#1262](https://github.com/stripe/stripe-php/pull/1262) Add support for updating the version of the repo +* [#1230](https://github.com/stripe/stripe-php/pull/1230) Add PHPDoc return types +* [#1242](https://github.com/stripe/stripe-php/pull/1242) Fix some PHPDoc in tests + +## 7.122.0 - 2022-04-08 +* [#1261](https://github.com/stripe/stripe-php/pull/1261) API Updates + * Add support for `apply_customer_balance` method on resource `PaymentIntent` +* [#1259](https://github.com/stripe/stripe-php/pull/1259) API Updates + + * Add `payment_intent.partially_funded`, `terminal.reader.action_failed`, and `terminal.reader.action_succeeded` events. + +## 7.121.0 - 2022-03-30 +* [#1258](https://github.com/stripe/stripe-php/pull/1258) API Updates + * Add support for `cancel_action`, `process_payment_intent`, `process_setup_intent`, and `set_reader_display` methods on resource `Terminal.Reader` + * Add support for `action` on `Terminal.Reader` + +## 7.120.0 - 2022-03-29 +* [#1257](https://github.com/stripe/stripe-php/pull/1257) API Updates + * Add support for Search API + * Add support for `search` method on resources `Charge`, `Customer`, `Invoice`, `PaymentIntent`, `Price`, `Product`, and `Subscription` + +## 7.119.0 - 2022-03-25 +* [#1256](https://github.com/stripe/stripe-php/pull/1256) API Updates + * Add support for PayNow and US Bank Accounts Debits payments + * Add support for `paynow` and `us_bank_account` on `PaymentMethod` + * Add support for new values `paynow` and `us_bank_account` on enum `PaymentMethod.type` + * Add support for `failure_balance_transaction` on `Charge` + +## 7.118.0 - 2022-03-23 +* [#1255](https://github.com/stripe/stripe-php/pull/1255) API Updates + * Add support for `cancel` method on resource `Refund` + * Add support for new values `bg_uic`, `hu_tin`, and `si_tin` on enum `TaxId.type` + * Add `test_helpers.test_clock.advancing`, `test_helpers.test_clock.created`, `test_helpers.test_clock.deleted`, `test_helpers.test_clock.internal_failure`, and `test_helpers.test_clock.ready` events. + + +## 7.117.0 - 2022-03-18 +* [#1254](https://github.com/stripe/stripe-php/pull/1254) API Updates + * Add support for `status` on `Card` +* [#1251](https://github.com/stripe/stripe-php/pull/1251) Add support for SearchResult objects. +* [#1249](https://github.com/stripe/stripe-php/pull/1249) Add missing constant for payment_behavior + +## 7.116.0 - 2022-03-02 +* [#1248](https://github.com/stripe/stripe-php/pull/1248) API Updates + * Add support for `proration_details` on `InvoiceLineItem` + + +## 7.115.0 - 2022-03-01 +* [#1245](https://github.com/stripe/stripe-php/pull/1245) [#1247](https://github.com/stripe/stripe-php/pull/1247) API Updates + * Add support for new resource `TestHelpers.TestClock` + * Add support for `test_clock` on `Customer`, `Invoice`, `InvoiceItem`, `Quote`, `Subscription`, and `SubscriptionSchedule` + * Add support for `next_action` on `Refund` + * Add support for `konbini` on `PaymentMethod` +* [#1244](https://github.com/stripe/stripe-php/pull/1244) API Updates + * Add support for new values `bbpos_wisepad3` and `stripe_m2` on enum `Terminal.Reader.device_type` + +## 7.114.0 - 2022-02-15 +* [#1243](https://github.com/stripe/stripe-php/pull/1243) Add test +* [#1240](https://github.com/stripe/stripe-php/pull/1240) API Updates + * Add support for `verify_microdeposits` method on resources `PaymentIntent` and `SetupIntent` +* [#1241](https://github.com/stripe/stripe-php/pull/1241) Add generic parameter to \Stripe\Collection usages + +## 7.113.0 - 2022-02-03 +* [#1239](https://github.com/stripe/stripe-php/pull/1239) API Updates + * Add `REASON_EXPIRED_UNCAPTURED_CHARGE` enum value on `Refund`. + +## 7.112.0 - 2022-01-25 +* [#1235](https://github.com/stripe/stripe-php/pull/1235) API Updates + * Add support for `phone_number_collection` on `PaymentLink` + * Add support for new value `is_vat` on enum `TaxId.type` + + +## 7.111.0 - 2022-01-20 +* [#1233](https://github.com/stripe/stripe-php/pull/1233) API Updates + * Add support for new resource `PaymentLink` + * Add support for `payment_link` on `Checkout.Session` + +## 7.110.0 - 2022-01-13 +* [#1232](https://github.com/stripe/stripe-php/pull/1232) API Updates + * Add support for `paid_out_of_band` on `Invoice` + +## 7.109.0 - 2022-01-12 +* [#1231](https://github.com/stripe/stripe-php/pull/1231) API Updates + * Add support for `customer_creation` on `Checkout.Session` +* [#1227](https://github.com/stripe/stripe-php/pull/1227) Update docs URLs + +## 7.108.0 - 2021-12-22 +* [#1226](https://github.com/stripe/stripe-php/pull/1226) Upgrade php-cs-fixer to 3.4.0. +* [#1222](https://github.com/stripe/stripe-php/pull/1222) API Updates + * Add support for `processing` on `PaymentIntent` +* [#1220](https://github.com/stripe/stripe-php/pull/1220) API Updates + +## 7.107.0 - 2021-12-09 +* [#1219](https://github.com/stripe/stripe-php/pull/1219) API Updates + * Add support for `metadata` on `BillingPortal.Configuration` + * Add support for `wallets` on `Issuing.Card` + +## 7.106.0 - 2021-12-09 +* [#1218](https://github.com/stripe/stripe-php/pull/1218) API Updates + * Add support for new values `ge_vat` and `ua_vat` on enum `TaxId.type` +* [#1216](https://github.com/stripe/stripe-php/pull/1216) Fix namespaced classes in @return PHPDoc. +* [#1214](https://github.com/stripe/stripe-php/pull/1214) Announce PHP8 support in CHANGELOG.md + +## 7.105.0 - 2021-12-06 +* [#1213](https://github.com/stripe/stripe-php/pull/1213) PHP 8.1 missing ReturnTypeWillChange annotations. +* As of this version, PHP 8.1 is officially supported. + +## 7.104.0 - 2021-12-01 +* [#1211](https://github.com/stripe/stripe-php/pull/1211) PHPStan compatibility with PHP8.x +* [#1209](https://github.com/stripe/stripe-php/pull/1209) PHPUnit compatibility with PHP 8.x + +## 7.103.0 - 2021-11-19 +* [#1206](https://github.com/stripe/stripe-php/pull/1206) API Updates + * Add support for new value `jct` on enum `TaxRate.tax_type` + +## 7.102.0 - 2021-11-17 +* [#1205](https://github.com/stripe/stripe-php/pull/1205) API Updates + * Add support for `automatic_payment_methods` on `PaymentIntent` + +## 7.101.0 - 2021-11-16 +* [#1203](https://github.com/stripe/stripe-php/pull/1203) API Updates + * Add support for new resource `ShippingRate` + * Add support for `shipping_options` and `shipping_rate` on `Checkout.Session` + * Add support for `expire` method on resource `Checkout.Session` + * Add support for `status` on `Checkout.Session` + +## 7.100.0 - 2021-10-11 +* [#1190](https://github.com/stripe/stripe-php/pull/1190) API Updates + * Add support for `klarna` on `PaymentMethod`. + +## 7.99.0 - 2021-10-11 +* [#1188](https://github.com/stripe/stripe-php/pull/1188) API Updates + * Add support for `list_payment_methods` method on resource `Customer` + +## 7.98.0 - 2021-10-07 +* [#1187](https://github.com/stripe/stripe-php/pull/1187) API Updates + * Add support for `phone_number_collection` on `Checkout.Session` + * Add support for new value `customer_id` on enum `Radar.ValueList.item_type` + * Add support for new value `bbpos_wisepos_e` on enum `Terminal.Reader.device_type` + +## 7.97.0 - 2021-09-16 +* [#1181](https://github.com/stripe/stripe-php/pull/1181) API Updates + * Add support for `full_name_aliases` on `Person` + +## 7.96.0 - 2021-09-15 +* [#1178](https://github.com/stripe/stripe-php/pull/1178) API Updates + * Add support for livemode on Reporting.ReportType + * Add support for new value `rst` on enum `TaxRate.tax_type` + +## 7.95.0 - 2021-09-01 +* [#1177](https://github.com/stripe/stripe-php/pull/1177) API Updates + * Add support for `future_requirements` on `Account`, `Capability`, and `Person` + * Add support for `after_expiration`, `consent`, `consent_collection`, `expires_at`, and `recovered_from` on `Checkout.Session` + +## 7.94.0 - 2021-08-19 +* [#1173](https://github.com/stripe/stripe-php/pull/1173) API Updates + * Add support for new value `fil` on enum `Checkout.Session.locale` + * Add support for new value `au_arn` on enum `TaxId.type` + +## 7.93.0 - 2021-08-11 +* [#1172](https://github.com/stripe/stripe-php/pull/1172) API Updates + * Add support for `locale` on `BillingPortal.Session` + +* [#1171](https://github.com/stripe/stripe-php/pull/1171) Fix typo in docblock `CurlClient::executeStreamingRequestWithRetries` + +## 7.92.0 - 2021-07-28 +* [#1167](https://github.com/stripe/stripe-php/pull/1167) API Updates + * Add support for `account_type` on `BankAccount` + * Add support for new value `redacted` on enum `Review.closed_reason` + +## 7.91.0 - 2021-07-22 +* [#1164](https://github.com/stripe/stripe-php/pull/1164) API Updates + * Add support for new values `hr`, `ko`, and `vi` on enum `Checkout.Session.locale` + * Add support for `payment_settings` on `Subscription` + +## 7.90.0 - 2021-07-20 +* [#1163](https://github.com/stripe/stripe-php/pull/1163) API Updates + * Add support for `wallet` on `Issuing.Transaction` +* [#1160](https://github.com/stripe/stripe-php/pull/1160) Remove unused API error types from docs. + +## 7.89.0 - 2021-07-14 +* [#1158](https://github.com/stripe/stripe-php/pull/1158) API Updates + * Add support for `list_computed_upfront_line_items` method on resource `Quote` +* [#1157](https://github.com/stripe/stripe-php/pull/1157) Improve readme for old PHP versions + +## 7.88.0 - 2021-07-09 +* [#1152](https://github.com/stripe/stripe-php/pull/1152) API Updates + * Add support for new resource `Quote` + * Add support for `quote` on `Invoice` + * Add support for new value `quote_accept` on enum `Invoice.billing_reason` +* [#1155](https://github.com/stripe/stripe-php/pull/1155) Add streaming methods to Service infra + * Add support for `setStreamingHttpClient` and `streamingHttpClient` to `ApiRequestor` + * Add support for `getStreamingClient` and `requestStream` to `AbstractService` + * Add support for `requestStream` to `BaseStripeClient` + * `\Stripe\RequestOptions::parse` now clones its input if it is already a `RequestOptions` object, to prevent accidental mutation. +* [#1151](https://github.com/stripe/stripe-php/pull/1151) Add `mode` constants into Checkout\Session + +## 7.87.0 - 2021-06-30 +* [#1149](https://github.com/stripe/stripe-php/pull/1149) API Updates + * Add support for `wechat_pay` on `PaymentMethod` +* [#1143](https://github.com/stripe/stripe-php/pull/1143) Streaming requests +* [#1138](https://github.com/stripe/stripe-php/pull/1138) Deprecate travis + +## 7.86.0 - 2021-06-25 +* [#1145](https://github.com/stripe/stripe-php/pull/1145) API Updates + * Add support for `boleto` on `PaymentMethod`. + * Add support for `il_vat` as a member of the `TaxID.Type` enum. + +## 7.85.0 - 2021-06-18 +* [#1142](https://github.com/stripe/stripe-php/pull/1142) API Updates + * Add support for new TaxId types: `ca_pst_mb`, `ca_pst_bc`, `ca_gst_hst`, and `ca_pst_sk`. + +## 7.84.0 - 2021-06-16 +* [#1141](https://github.com/stripe/stripe-php/pull/1141) Update PHPDocs + * Add support for `url` on `Checkout\Session` + + +## 7.83.0 - 2021-06-07 +* [#1140](https://github.com/stripe/stripe-php/pull/1140) API Updates + * Added support for `tax_id_collection` on `Checkout\Session` and `Checkout\Session#create` + * Update `Location` to be expandable on `Terminal\Reader` + +## 7.82.0 - 2021-06-04 +* [#1136](https://github.com/stripe/stripe-php/pull/1136) Update PHPDocs + * Add support for `controller` on `Account`. + +## 7.81.0 - 2021-06-04 +* [#1135](https://github.com/stripe/stripe-php/pull/1135) API Updates + * Add support for new resource `TaxCode` + * Add support for `automatic_tax` `Invoice` and`Checkout.Session`. + * Add support for `tax_behavior` on `Price` + * Add support for `tax_code` on `Product` + * Add support for `tax` on `Customer` + * Add support for `tax_type` enum on `TaxRate` + +## 7.80.0 - 2021-05-26 +* [#1130](https://github.com/stripe/stripe-php/pull/1130) Update PHPDocs + +## 7.79.0 - 2021-05-19 +* [#1126](https://github.com/stripe/stripe-php/pull/1126) API Updates + * Added support for new resource `Identity.VerificationReport` + * Added support for new resource `Identity.VerificationSession` + * `File#list.purpose` and `File.purpose` added new enum members: `identity_document_downloadable` and `selfie`. + +## 7.78.0 - 2021-05-05 +* [#1120](https://github.com/stripe/stripe-php/pull/1120) Update PHPDocs + * Add support for `Radar.EarlyFraudWarning.payment_intent` + +## 7.77.0 - 2021-04-12 +* [#1110](https://github.com/stripe/stripe-php/pull/1110) Update PHPDocs + * Add support for `acss_debit` on `PaymentMethod` + * Add support for `payment_method_options` on `Checkout\Session` +* [#1107](https://github.com/stripe/stripe-php/pull/1107) Remove duplicate object phpdoc + +## 7.76.0 - 2021-03-22 +* [#1100](https://github.com/stripe/stripe-php/pull/1100) Update PHPDocs + * Added support for `amount_shipping` on `Checkout.Session.total_details` +* [#1088](https://github.com/stripe/stripe-php/pull/1088) Make possibility to extend CurlClient + +## 7.75.0 - 2021-02-22 +* [#1094](https://github.com/stripe/stripe-php/pull/1094) Add support for Billing Portal Configuration API + +## 7.74.0 - 2021-02-17 +* [#1093](https://github.com/stripe/stripe-php/pull/1093) Update PHPDocs + * Add support for on_behalf_of to Invoice + +## 7.73.0 - 2021-02-16 +* [#1091](https://github.com/stripe/stripe-php/pull/1091) Update PHPDocs + * Add support for `afterpay_clearpay` on `PaymentMethod`. + +## 7.72.0 - 2021-02-08 +* [#1089](https://github.com/stripe/stripe-php/pull/1089) Update PHPDocs + * Add support for `afterpay_clearpay_payments` on `Account.capabilities` + * Add support for `payment_settings` on `Invoice` + +## 7.71.0 - 2021-02-05 +* [#1087](https://github.com/stripe/stripe-php/pull/1087) Update PHPDocs +* [#1086](https://github.com/stripe/stripe-php/pull/1086) Update CA cert bundle URL + +## 7.70.0 - 2021-02-03 +* [#1085](https://github.com/stripe/stripe-php/pull/1085) Update PHPDocs + * Add support for `nationality` on `Person` + * Add member `gb_vat` of `TaxID` enum + + +## 7.69.0 - 2021-01-21 +* [#1079](https://github.com/stripe/stripe-php/pull/1079) Update PHPDocs + +## 7.68.0 - 2021-01-14 +* [#1063](https://github.com/stripe/stripe-php/pull/1063) Multiple API changes +* [#1061](https://github.com/stripe/stripe-php/pull/1061) Bump phpDocumentor to 3.0.0 + +## 7.67.0 - 2020-12-09 +* [#1060](https://github.com/stripe/stripe-php/pull/1060) Improve PHPDocs for `Discount` +* [#1059](https://github.com/stripe/stripe-php/pull/1059) Upgrade PHPStan to 0.12.59 +* [#1057](https://github.com/stripe/stripe-php/pull/1057) Bump PHP-CS-Fixer and update code + +## 7.66.1 - 2020-12-01 +* [#1054](https://github.com/stripe/stripe-php/pull/1054) Improve error message for invalid keys in StripeClient + +## 7.66.0 - 2020-11-24 +* [#1053](https://github.com/stripe/stripe-php/pull/1053) Update PHPDocs + +## 7.65.0 - 2020-11-19 +* [#1050](https://github.com/stripe/stripe-php/pull/1050) Added constants for `proration_behavior` on `Subscription` + +## 7.64.0 - 2020-11-18 +* [#1049](https://github.com/stripe/stripe-php/pull/1049) Update PHPDocs + +## 7.63.0 - 2020-11-17 +* [#1048](https://github.com/stripe/stripe-php/pull/1048) Update PHPDocs +* [#1046](https://github.com/stripe/stripe-php/pull/1046) Force IPv4 resolving + +## 7.62.0 - 2020-11-09 +* [#1041](https://github.com/stripe/stripe-php/pull/1041) Add missing constants on `Event` +* [#1038](https://github.com/stripe/stripe-php/pull/1038) Update PHPDocs + +## 7.61.0 - 2020-10-20 +* [#1030](https://github.com/stripe/stripe-php/pull/1030) Add support for `jp_rn` and `ru_kpp` as a `type` on `TaxId` + +## 7.60.0 - 2020-10-15 +* [#1027](https://github.com/stripe/stripe-php/pull/1027) Warn if opts are in params + +## 7.58.0 - 2020-10-14 +* [#1026](https://github.com/stripe/stripe-php/pull/1026) Add support for the Payout Reverse API + +## 7.57.0 - 2020-09-29 +* [#1020](https://github.com/stripe/stripe-php/pull/1020) Add support for the `SetupAttempt` resource and List API + +## 7.56.0 - 2020-09-25 +* [#1019](https://github.com/stripe/stripe-php/pull/1019) Update PHPDocs + +## 7.55.0 - 2020-09-24 +* [#1018](https://github.com/stripe/stripe-php/pull/1018) Multiple API changes + * Updated PHPDocs + * Added `TYPE_CONTRIBUTION` as a constant on `BalanceTransaction` + +## 7.54.0 - 2020-09-23 +* [#1017](https://github.com/stripe/stripe-php/pull/1017) Updated PHPDoc + +## 7.53.1 - 2020-09-22 +* [#1015](https://github.com/stripe/stripe-php/pull/1015) Bugfix: don't error on systems with php_uname in disablefunctions with whitespace + +## 7.53.0 - 2020-09-21 +* [#1016](https://github.com/stripe/stripe-php/pull/1016) Updated PHPDocs + +## 7.52.0 - 2020-09-08 +* [#1010](https://github.com/stripe/stripe-php/pull/1010) Update PHPDocs + +## 7.51.0 - 2020-09-02 +* [#1007](https://github.com/stripe/stripe-php/pull/1007) Multiple API changes + * Add support for the Issuing Dispute Submit API + * Add constants for `payment_status` on Checkout `Session` +* [#1003](https://github.com/stripe/stripe-php/pull/1003) Add trim to getSignatures to allow for leading whitespace. + +## 7.50.0 - 2020-08-28 +* [#1005](https://github.com/stripe/stripe-php/pull/1005) Updated PHPDocs + +## 7.49.0 - 2020-08-19 +* [#998](https://github.com/stripe/stripe-php/pull/998) PHPDocs updated + +## 7.48.0 - 2020-08-17 +* [#997](https://github.com/stripe/stripe-php/pull/997) PHPDocs updated +* [#996](https://github.com/stripe/stripe-php/pull/996) Fixing telemetry + +## 7.47.0 - 2020-08-13 +* [#994](https://github.com/stripe/stripe-php/pull/994) Nullable balance_transactions on issuing disputes +* [#991](https://github.com/stripe/stripe-php/pull/991) Fix invalid return types in OAuthService + +## 7.46.1 - 2020-08-07 +* [#990](https://github.com/stripe/stripe-php/pull/990) PHPdoc changes + +## 7.46.0 - 2020-08-05 +* [#989](https://github.com/stripe/stripe-php/pull/989) Add support for the `PromotionCode` resource and APIs + +## 7.45.0 - 2020-07-28 +* [#981](https://github.com/stripe/stripe-php/pull/981) PHPdoc updates + +## 7.44.0 - 2020-07-20 +* [#948](https://github.com/stripe/stripe-php/pull/948) Add `first()` and `last()` functions to `Collection` + +## 7.43.0 - 2020-07-17 +* [#975](https://github.com/stripe/stripe-php/pull/975) Add support for `political_exposure` on `Person` + +## 7.42.0 - 2020-07-15 +* [#974](https://github.com/stripe/stripe-php/pull/974) Add new constants for `purpose` on `File` + +## 7.41.1 - 2020-07-15 +* [#973](https://github.com/stripe/stripe-php/pull/973) Multiple PHPDoc fixes + +## 7.41.0 - 2020-07-14 +* [#971](https://github.com/stripe/stripe-php/pull/971) Adds enum values for `billing_address_collection` on Checkout `Session` + +## 7.40.0 - 2020-07-06 +* [#964](https://github.com/stripe/stripe-php/pull/964) Add OAuthService + +## 7.39.0 - 2020-06-25 +* [#960](https://github.com/stripe/stripe-php/pull/960) Add constants for `payment_behavior` on `Subscription` + +## 7.38.0 - 2020-06-24 +* [#959](https://github.com/stripe/stripe-php/pull/959) Add multiple constants missing for `Event` + +## 7.37.2 - 2020-06-23 +* [#957](https://github.com/stripe/stripe-php/pull/957) Updated PHPDocs + +## 7.37.1 - 2020-06-11 +* [#952](https://github.com/stripe/stripe-php/pull/952) Improve PHPDoc + +## 7.37.0 - 2020-06-09 +* [#950](https://github.com/stripe/stripe-php/pull/950) Add support for `id_npwp` and `my_frp` as `type` on `TaxId` + +## 7.36.2 - 2020-06-03 +* [#946](https://github.com/stripe/stripe-php/pull/946) Update PHPDoc + +## 7.36.1 - 2020-05-28 +* [#938](https://github.com/stripe/stripe-php/pull/938) Remove extra array_keys() call. +* [#942](https://github.com/stripe/stripe-php/pull/942) fix autopagination for service methods + +## 7.36.0 - 2020-05-21 +* [#937](https://github.com/stripe/stripe-php/pull/937) Add support for `ae_trn`, `cl_tin` and `sa_vat` as `type` on `TaxId` + +## 7.35.0 - 2020-05-20 +* [#936](https://github.com/stripe/stripe-php/pull/936) Add `anticipation_repayment` as a `type` on `BalanceTransaction` + +## 7.34.0 - 2020-05-18 +* [#934](https://github.com/stripe/stripe-php/pull/934) Add support for `issuing_dispute` as a `type` on `BalanceTransaction` + +## 7.33.1 - 2020-05-15 +* [#933](https://github.com/stripe/stripe-php/pull/933) Services bugfix: convert nested null params to empty strings + +## 7.33.0 - 2020-05-14 +* [#771](https://github.com/stripe/stripe-php/pull/771) Introduce client/services API. The [migration guide](https://github.com/stripe/stripe-php/wiki/Migration-to-StripeClient-and-services-in-7.33.0) contains before & after examples of the backwards-compatible changes. + +## 7.32.1 - 2020-05-13 +* [#932](https://github.com/stripe/stripe-php/pull/932) Fix multiple PHPDoc + +## 7.32.0 - 2020-05-11 +* [#931](https://github.com/stripe/stripe-php/pull/931) Add support for the `LineItem` resource and APIs + +## 7.31.0 - 2020-05-01 +* [#927](https://github.com/stripe/stripe-php/pull/927) Add support for new tax IDs + +## 7.30.0 - 2020-04-29 +* [#924](https://github.com/stripe/stripe-php/pull/924) Add support for the `Price` resource and APIs + +## 7.29.0 - 2020-04-22 +* [#920](https://github.com/stripe/stripe-php/pull/920) Add support for the `Session` resource and APIs on the `BillingPortal` namespace + +## 7.28.1 - 2020-04-10 +* [#915](https://github.com/stripe/stripe-php/pull/915) Improve PHPdocs for many classes + +## 7.28.0 - 2020-04-03 +* [#912](https://github.com/stripe/stripe-php/pull/912) Preserve backwards compatibility for typoed `TYPE_ADJUSTEMENT` enum. +* [#911](https://github.com/stripe/stripe-php/pull/911) Codegenerated PHPDoc for nested resources +* [#902](https://github.com/stripe/stripe-php/pull/902) Update docstrings for nested resources + +## 7.27.3 - 2020-03-18 +* [#899](https://github.com/stripe/stripe-php/pull/899) Convert keys to strings in `StripeObject::toArray()` + +## 7.27.2 - 2020-03-13 +* [#894](https://github.com/stripe/stripe-php/pull/894) Multiple PHPDocs changes + +## 7.27.1 - 2020-03-03 +* [#890](https://github.com/stripe/stripe-php/pull/890) Update PHPdoc + +## 7.27.0 - 2020-02-28 +* [#889](https://github.com/stripe/stripe-php/pull/889) Add new constants for `type` on `TaxId` + +## 7.26.0 - 2020-02-26 +* [#886](https://github.com/stripe/stripe-php/pull/886) Add support for listing Checkout `Session` +* [#883](https://github.com/stripe/stripe-php/pull/883) Add PHPDoc class descriptions + +## 7.25.0 - 2020-02-14 +* [#879](https://github.com/stripe/stripe-php/pull/879) Make `\Stripe\Collection` implement `\Countable` +* [#875](https://github.com/stripe/stripe-php/pull/875) Last set of PHP-CS-Fixer updates +* [#874](https://github.com/stripe/stripe-php/pull/874) Enable php_unit_internal_class rule +* [#873](https://github.com/stripe/stripe-php/pull/873) Add support for phpDocumentor in Makefile +* [#872](https://github.com/stripe/stripe-php/pull/872) Another batch of PHP-CS-Fixer rule updates +* [#871](https://github.com/stripe/stripe-php/pull/871) Fix a few PHPDoc comments +* [#870](https://github.com/stripe/stripe-php/pull/870) More PHP-CS-Fixer tweaks + +## 7.24.0 - 2020-02-10 +* [#862](https://github.com/stripe/stripe-php/pull/862) Better PHPDoc +* [#865](https://github.com/stripe/stripe-php/pull/865) Get closer to `@PhpCsFixer` standard ruleset + +## 7.23.0 - 2020-02-05 +* [#860](https://github.com/stripe/stripe-php/pull/860) Add PHPDoc types for expandable fields +* [#858](https://github.com/stripe/stripe-php/pull/858) Use `native_function_invocation` PHPStan rule +* [#857](https://github.com/stripe/stripe-php/pull/857) Update PHPDoc on nested resources +* [#855](https://github.com/stripe/stripe-php/pull/855) PHPDoc: `StripeObject` -> `ErrorObject` where appropriate +* [#837](https://github.com/stripe/stripe-php/pull/837) Autogen diff +* [#854](https://github.com/stripe/stripe-php/pull/854) Upgrade PHPStan and fix settings +* [#850](https://github.com/stripe/stripe-php/pull/850) Yet more PHPDoc updates + +## 7.22.0 - 2020-01-31 +* [#849](https://github.com/stripe/stripe-php/pull/849) Add new constants for `type` on `TaxId` +* [#843](https://github.com/stripe/stripe-php/pull/843) Even more PHPDoc fixes +* [#841](https://github.com/stripe/stripe-php/pull/841) More PHPDoc fixes + +## 7.21.1 - 2020-01-29 +* [#840](https://github.com/stripe/stripe-php/pull/840) Update phpdocs across multiple resources. + +## 7.21.0 - 2020-01-28 +* [#839](https://github.com/stripe/stripe-php/pull/839) Add support for `TYPE_ES_CIF` on `TaxId` + +## 7.20.0 - 2020-01-23 +* [#836](https://github.com/stripe/stripe-php/pull/836) Add new type values for `TaxId` + +## 7.19.1 - 2020-01-14 +* [#831](https://github.com/stripe/stripe-php/pull/831) Fix incorrect `UnexpectedValueException` instantiation + +## 7.19.0 - 2020-01-14 +* [#830](https://github.com/stripe/stripe-php/pull/830) Add support for `CreditNoteLineItem` + +## 7.18.0 - 2020-01-13 +* [#829](https://github.com/stripe/stripe-php/pull/829) Don't call php_uname function if disabled by php.ini + +## 7.17.0 - 2020-01-08 +* [#821](https://github.com/stripe/stripe-php/pull/821) Improve PHPDoc types for `ApiErrorException.get/setJsonBody()` methods + +## 7.16.0 - 2020-01-06 +* [#826](https://github.com/stripe/stripe-php/pull/826) Rename remaining `$options` to `$opts` +* [#825](https://github.com/stripe/stripe-php/pull/825) Update PHPDoc + +## 7.15.0 - 2020-01-06 +* [#824](https://github.com/stripe/stripe-php/pull/824) Add constant `TYPE_SG_UEN` to `TaxId` + +## 7.14.2 - 2019-12-04 +* [#816](https://github.com/stripe/stripe-php/pull/816) Disable autoloader when checking for `Throwable` + +## 7.14.1 - 2019-11-26 +* [#812](https://github.com/stripe/stripe-php/pull/812) Fix invalid PHPdoc on `Subscription` + +## 7.14.0 - 2019-11-26 +* [#811](https://github.com/stripe/stripe-php/pull/811) Add support for `CreditNote` preview. + +## 7.13.0 - 2019-11-19 +* [#808](https://github.com/stripe/stripe-php/pull/808) Add support for listing lines on an Invoice directly via `Invoice::allLines()` + +## 7.12.0 - 2019-11-08 + +- [#805](https://github.com/stripe/stripe-php/pull/805) Add Source::allSourceTransactions and SubscriptionItem::allUsageRecordSummaries +- [#798](https://github.com/stripe/stripe-php/pull/798) The argument of `array_key_exists` cannot be `null` +- [#803](https://github.com/stripe/stripe-php/pull/803) Removed unwanted got + +## 7.11.0 - 2019-11-06 + +- [#797](https://github.com/stripe/stripe-php/pull/797) Add support for reverse pagination + +## 7.10.0 - 2019-11-05 + +- [#795](https://github.com/stripe/stripe-php/pull/795) Add support for `Mandate` + +## 7.9.0 - 2019-11-05 + +- [#794](https://github.com/stripe/stripe-php/pull/794) Add PHPDoc to `ApiResponse` +- [#792](https://github.com/stripe/stripe-php/pull/792) Use single quotes for `OBJECT_NAME` constants + +## 7.8.0 - 2019-11-05 + +- [#790](https://github.com/stripe/stripe-php/pull/790) Mark nullable fields in PHPDoc +- [#788](https://github.com/stripe/stripe-php/pull/788) Early codegen fixes +- [#787](https://github.com/stripe/stripe-php/pull/787) Use PHPStan in Travis CI + +## 7.7.1 - 2019-10-25 + +- [#781](https://github.com/stripe/stripe-php/pull/781) Fix telemetry header +- [#780](https://github.com/stripe/stripe-php/pull/780) Contributor Convenant + +## 7.7.0 - 2019-10-23 + +- [#776](https://github.com/stripe/stripe-php/pull/776) Add `CAPABILITY_TRANSFERS` to `Account` +- [#778](https://github.com/stripe/stripe-php/pull/778) Add support for `TYPE_MX_RFC` type on `TaxId` + +## 7.6.0 - 2019-10-22 + +- [#770](https://github.com/stripe/stripe-php/pull/770) Add missing constants for Customer's `TaxId` + +## 7.5.0 - 2019-10-18 + +- [#768](https://github.com/stripe/stripe-php/pull/768) Redact API key in `RequestOptions` debug info + +## 7.4.0 - 2019-10-15 + +- [#764](https://github.com/stripe/stripe-php/pull/764) Add support for HTTP request monitoring callback + +## 7.3.1 - 2019-10-07 + +- [#755](https://github.com/stripe/stripe-php/pull/755) Respect Stripe-Should-Retry and Retry-After headers + +## 7.3.0 - 2019-10-02 + +- [#752](https://github.com/stripe/stripe-php/pull/752) Add `payment_intent.canceled` and `setup_intent.canceled` events +- [#749](https://github.com/stripe/stripe-php/pull/749) Call `toArray()` on objects only + +## 7.2.2 - 2019-09-24 + +- [#746](https://github.com/stripe/stripe-php/pull/746) Add missing decline codes + +## 7.2.1 - 2019-09-23 + +- [#744](https://github.com/stripe/stripe-php/pull/744) Added new PHPDoc + +## 7.2.0 - 2019-09-17 + +- [#738](https://github.com/stripe/stripe-php/pull/738) Added missing constants for `SetupIntent` events + +## 7.1.1 - 2019-09-16 + +- [#737](https://github.com/stripe/stripe-php/pull/737) Added new PHPDoc + +## 7.1.0 - 2019-09-13 + +- [#736](https://github.com/stripe/stripe-php/pull/736) Make `CaseInsensitiveArray` countable and traversable + +## 7.0.2 - 2019-09-06 + +- [#729](https://github.com/stripe/stripe-php/pull/729) Fix usage of `SignatureVerificationException` in PHPDoc blocks + +## 7.0.1 - 2019-09-05 + +- [#728](https://github.com/stripe/stripe-php/pull/728) Clean up Collection + +## 7.0.0 - 2019-09-03 + +Major version release. The [migration guide](https://github.com/stripe/stripe-php/wiki/Migration-guide-for-v7) contains a detailed list of backwards-incompatible changes with upgrade instructions. + +Pull requests included in this release (cf. [#552](https://github.com/stripe/stripe-php/pull/552)) (⚠️ = breaking changes): + +- ⚠️ Drop support for PHP 5.4 ([#551](https://github.com/stripe/stripe-php/pull/551)) +- ⚠️ Drop support for PHP 5.5 ([#554](https://github.com/stripe/stripe-php/pull/554)) +- Bump dependencies ([#553](https://github.com/stripe/stripe-php/pull/553)) +- Remove `CURLFile` check ([#555](https://github.com/stripe/stripe-php/pull/555)) +- Update constant definitions for PHP >= 5.6 ([#556](https://github.com/stripe/stripe-php/pull/556)) +- ⚠️ Remove `FileUpload` alias ([#557](https://github.com/stripe/stripe-php/pull/557)) +- Remove `curl_reset` check ([#570](https://github.com/stripe/stripe-php/pull/570)) +- Use `\Stripe\::class` constant instead of strings ([#643](https://github.com/stripe/stripe-php/pull/643)) +- Use `array_column` to flatten params ([#686](https://github.com/stripe/stripe-php/pull/686)) +- ⚠️ Remove deprecated methods ([#692](https://github.com/stripe/stripe-php/pull/692)) +- ⚠️ Remove `IssuerFraudRecord` ([#696](https://github.com/stripe/stripe-php/pull/696)) +- Update constructors of Stripe exception classes ([#559](https://github.com/stripe/stripe-php/pull/559)) +- Fix remaining TODOs ([#700](https://github.com/stripe/stripe-php/pull/700)) +- Use yield for autopagination ([#703](https://github.com/stripe/stripe-php/pull/703)) +- ⚠️ Rename fake magic methods and rewrite array conversion ([#704](https://github.com/stripe/stripe-php/pull/704)) +- Add `ErrorObject` to Stripe exceptions ([#705](https://github.com/stripe/stripe-php/pull/705)) +- Start using PHP CS Fixer ([#706](https://github.com/stripe/stripe-php/pull/706)) +- Update error messages for nested resource operations ([#708](https://github.com/stripe/stripe-php/pull/708)) +- Upgrade retry logic ([#707](https://github.com/stripe/stripe-php/pull/707)) +- ⚠️ `Collection` improvements / fixes ([#715](https://github.com/stripe/stripe-php/pull/715)) +- ⚠️ Modernize exceptions ([#709](https://github.com/stripe/stripe-php/pull/709)) +- Add constants for error codes ([#716](https://github.com/stripe/stripe-php/pull/716)) +- Update certificate bundle ([#717](https://github.com/stripe/stripe-php/pull/717)) +- Retry requests on a 429 that's a lock timeout ([#718](https://github.com/stripe/stripe-php/pull/718)) +- Fix `toArray()` calls ([#719](https://github.com/stripe/stripe-php/pull/719)) +- Couple of fixes for PHP 7.4 ([#725](https://github.com/stripe/stripe-php/pull/725)) + +## 6.43.1 - 2019-08-29 + +- [#722](https://github.com/stripe/stripe-php/pull/722) Make `LoggerInterface::error` compatible with its PSR-3 counterpart +- [#714](https://github.com/stripe/stripe-php/pull/714) Add `pending_setup_intent` property in `Subscription` +- [#713](https://github.com/stripe/stripe-php/pull/713) Add typehint to `ApiResponse` +- [#712](https://github.com/stripe/stripe-php/pull/712) Fix comment +- [#701](https://github.com/stripe/stripe-php/pull/701) Start testing PHP 7.3 + +## 6.43.0 - 2019-08-09 + +- [#694](https://github.com/stripe/stripe-php/pull/694) Add `SubscriptionItem::createUsageRecord` method + +## 6.42.0 - 2019-08-09 + +- [#688](https://github.com/stripe/stripe-php/pull/688) Remove `SubscriptionScheduleRevision` + - Note that this is technically a breaking change, however we've chosen to release it as a minor version in light of the fact that this resource and its API methods were virtually unused. + +## 6.41.0 - 2019-07-31 + +- [#683](https://github.com/stripe/stripe-php/pull/683) Move the List Balance History API to `/v1/balance_transactions` + +## 6.40.0 - 2019-06-27 + +- [#675](https://github.com/stripe/stripe-php/pull/675) Add support for `SetupIntent` resource and APIs + +## 6.39.2 - 2019-06-26 + +- [#676](https://github.com/stripe/stripe-php/pull/676) Fix exception message in `CustomerBalanceTransaction::update()` + +## 6.39.1 - 2019-06-25 + +- [#674](https://github.com/stripe/stripe-php/pull/674) Add new constants for `collection_method` on `Invoice` + +## 6.39.0 - 2019-06-24 + +- [#673](https://github.com/stripe/stripe-php/pull/673) Enable request latency telemetry by default + +## 6.38.0 - 2019-06-17 + +- [#649](https://github.com/stripe/stripe-php/pull/649) Add support for `CustomerBalanceTransaction` resource and APIs + +## 6.37.2 - 2019-06-17 + +- [#671](https://github.com/stripe/stripe-php/pull/671) Add new PHPDoc +- [#672](https://github.com/stripe/stripe-php/pull/672) Add constants for `submit_type` on Checkout `Session` + +## 6.37.1 - 2019-06-14 + +- [#670](https://github.com/stripe/stripe-php/pull/670) Add new PHPDoc + +## 6.37.0 - 2019-05-23 + +- [#663](https://github.com/stripe/stripe-php/pull/663) Add support for `radar.early_fraud_warning` resource + +## 6.36.0 - 2019-05-22 + +- [#661](https://github.com/stripe/stripe-php/pull/661) Add constants for new TaxId types +- [#662](https://github.com/stripe/stripe-php/pull/662) Add constants for BalanceTransaction types + +## 6.35.2 - 2019-05-20 + +- [#655](https://github.com/stripe/stripe-php/pull/655) Add constants for payment intent statuses +- [#659](https://github.com/stripe/stripe-php/pull/659) Fix PHPDoc for various nested Account actions +- [#660](https://github.com/stripe/stripe-php/pull/660) Fix various PHPDoc + +## 6.35.1 - 2019-05-20 + +- [#658](https://github.com/stripe/stripe-php/pull/658) Use absolute value when checking timestamp tolerance + +## 6.35.0 - 2019-05-14 + +- [#651](https://github.com/stripe/stripe-php/pull/651) Add support for the Capability resource and APIs + +## 6.34.6 - 2019-05-13 + +- [#654](https://github.com/stripe/stripe-php/pull/654) Fix typo in definition of `Event::PAYMENT_METHOD_ATTACHED` constant + +## 6.34.5 - 2019-05-06 + +- [#647](https://github.com/stripe/stripe-php/pull/647) Set the return type to static for more operations + +## 6.34.4 - 2019-05-06 + +- [#650](https://github.com/stripe/stripe-php/pull/650) Add missing constants for Event types + +## 6.34.3 - 2019-05-01 + +- [#644](https://github.com/stripe/stripe-php/pull/644) Update return type to `static` to improve static analysis +- [#645](https://github.com/stripe/stripe-php/pull/645) Fix constant for `payment_intent.payment_failed` + +## 6.34.2 - 2019-04-26 + +- [#642](https://github.com/stripe/stripe-php/pull/642) Fix an issue where existing idempotency keys would be overwritten when using automatic retries + +## 6.34.1 - 2019-04-25 + +- [#640](https://github.com/stripe/stripe-php/pull/640) Add missing phpdocs + +## 6.34.0 - 2019-04-24 + +- [#626](https://github.com/stripe/stripe-php/pull/626) Add support for the `TaxRate` resource and APIs +- [#639](https://github.com/stripe/stripe-php/pull/639) Fix multiple phpdoc issues + +## 6.33.0 - 2019-04-22 + +- [#630](https://github.com/stripe/stripe-php/pull/630) Add support for the `TaxId` resource and APIs + +## 6.32.1 - 2019-04-19 + +- [#636](https://github.com/stripe/stripe-php/pull/636) Correct type of `$personId` in PHPDoc + +## 6.32.0 - 2019-04-18 + +- [#621](https://github.com/stripe/stripe-php/pull/621) Add support for `CreditNote` + +## 6.31.5 - 2019-04-12 + +- [#628](https://github.com/stripe/stripe-php/pull/628) Add constants for `person.*` event types +- [#628](https://github.com/stripe/stripe-php/pull/628) Add missing constants for `Account` and `Person` + +## 6.31.4 - 2019-04-05 + +- [#624](https://github.com/stripe/stripe-php/pull/624) Fix encoding of nested parameters in multipart requests + +## 6.31.3 - 2019-04-02 + +- [#623](https://github.com/stripe/stripe-php/pull/623) Only use HTTP/2 with curl >= 7.60.0 + +## 6.31.2 - 2019-03-25 + +- [#619](https://github.com/stripe/stripe-php/pull/619) Fix PHPDoc return types for list methods for nested resources + +## 6.31.1 - 2019-03-22 + +- [#612](https://github.com/stripe/stripe-php/pull/612) Add a lot of constants +- [#614](https://github.com/stripe/stripe-php/pull/614) Add missing subscription status constants + +## 6.31.0 - 2019-03-18 + +- [#600](https://github.com/stripe/stripe-php/pull/600) Add support for the `PaymentMethod` resource and APIs +- [#606](https://github.com/stripe/stripe-php/pull/606) Add support for retrieving a Checkout `Session` +- [#611](https://github.com/stripe/stripe-php/pull/611) Add support for deleting a Terminal `Location` and `Reader` + +## 6.30.5 - 2019-03-11 + +- [#607](https://github.com/stripe/stripe-php/pull/607) Correctly handle case where a metadata key is called `metadata` + +## 6.30.4 - 2019-02-27 + +- [#602](https://github.com/stripe/stripe-php/pull/602) Add `subscription_schedule` to `Subscription` for PHPDoc. + +## 6.30.3 - 2019-02-26 + +- [#603](https://github.com/stripe/stripe-php/pull/603) Improve PHPDoc on the `Source` object to cover all types of Sources currently supported. + +## 6.30.2 - 2019-02-25 + +- [#601](https://github.com/stripe/stripe-php/pull/601) Fix PHPDoc across multiple resources and add support for new events. + +## 6.30.1 - 2019-02-16 + +- [#599](https://github.com/stripe/stripe-php/pull/599) Fix PHPDoc for `SubscriptionSchedule` and `SubscriptionScheduleRevision` + +## 6.30.0 - 2019-02-12 + +- [#590](https://github.com/stripe/stripe-php/pull/590) Add support for `SubscriptionSchedule` and `SubscriptionScheduleRevision` + +## 6.29.3 - 2019-01-31 + +- [#592](https://github.com/stripe/stripe-php/pull/592) Some more PHPDoc fixes + +## 6.29.2 - 2019-01-31 + +- [#591](https://github.com/stripe/stripe-php/pull/591) Fix PHPDoc for nested resources + +## 6.29.1 - 2019-01-25 + +- [#566](https://github.com/stripe/stripe-php/pull/566) Fix dangling message contents +- [#586](https://github.com/stripe/stripe-php/pull/586) Don't overwrite `CURLOPT_HTTP_VERSION` option + +## 6.29.0 - 2019-01-23 + +- [#579](https://github.com/stripe/stripe-php/pull/579) Rename `CheckoutSession` to `Session` and move it under the `Checkout` namespace. This is a breaking change, but we've reached out to affected merchants and all new merchants would use the new approach. + +## 6.28.1 - 2019-01-21 + +- [#580](https://github.com/stripe/stripe-php/pull/580) Properly serialize `individual` on `Account` objects + +## 6.28.0 - 2019-01-03 + +- [#576](https://github.com/stripe/stripe-php/pull/576) Add support for iterating directly over `Collection` instances + +## 6.27.0 - 2018-12-21 + +- [#571](https://github.com/stripe/stripe-php/pull/571) Add support for the `CheckoutSession` resource + +## 6.26.0 - 2018-12-11 + +- [#568](https://github.com/stripe/stripe-php/pull/568) Enable persistent connections + +## 6.25.0 - 2018-12-10 + +- [#567](https://github.com/stripe/stripe-php/pull/567) Add support for account links + +## 6.24.0 - 2018-11-28 + +- [#562](https://github.com/stripe/stripe-php/pull/562) Add support for the Review resource +- [#564](https://github.com/stripe/stripe-php/pull/564) Add event name constants for subscription schedule aborted/expiring + +## 6.23.0 - 2018-11-27 + +- [#542](https://github.com/stripe/stripe-php/pull/542) Add support for `ValueList` and `ValueListItem` for Radar + +## 6.22.1 - 2018-11-20 + +- [#561](https://github.com/stripe/stripe-php/pull/561) Add cast and some docs to telemetry introduced in 6.22.0/549 + +## 6.22.0 - 2018-11-15 + +- [#549](https://github.com/stripe/stripe-php/pull/549) Add support for client telemetry + +## 6.21.1 - 2018-11-12 + +- [#548](https://github.com/stripe/stripe-php/pull/548) Don't mutate `Exception` class properties from `OAuthBase` error + +## 6.21.0 - 2018-11-08 + +- [#537](https://github.com/stripe/stripe-php/pull/537) Add new API endpoints for the `Invoice` resource. + +## 6.20.1 - 2018-11-07 + +- [#546](https://github.com/stripe/stripe-php/pull/546) Drop files from the Composer package that aren't needed in the release + +## 6.20.0 - 2018-10-30 + +- [#536](https://github.com/stripe/stripe-php/pull/536) Add support for the `Person` resource +- [#541](https://github.com/stripe/stripe-php/pull/541) Add support for the `WebhookEndpoint` resource + +## 6.19.5 - 2018-10-17 + +- [#539](https://github.com/stripe/stripe-php/pull/539) Fix methods on `\Stripe\PaymentIntent` to properly pass arguments to the API. + +## 6.19.4 - 2018-10-11 + +- [#534](https://github.com/stripe/stripe-php/pull/534) Fix PSR-4 autoloading for `\Stripe\FileUpload` class alias + +## 6.19.3 - 2018-10-09 + +- [#530](https://github.com/stripe/stripe-php/pull/530) Add constants for `flow` (`FLOW_*`), `status` (`STATUS_*`) and `usage` (`USAGE_*`) on `\Stripe\Source` + +## 6.19.2 - 2018-10-08 + +- [#531](https://github.com/stripe/stripe-php/pull/531) Store HTTP response headers in case-insensitive array + +## 6.19.1 - 2018-09-25 + +- [#526](https://github.com/stripe/stripe-php/pull/526) Ignore null values in request parameters + +## 6.19.0 - 2018-09-24 + +- [#523](https://github.com/stripe/stripe-php/pull/523) Add support for Stripe Terminal + +## 6.18.0 - 2018-09-24 + +- [#520](https://github.com/stripe/stripe-php/pull/520) Rename `\Stripe\FileUpload` to `\Stripe\File` + +## 6.17.2 - 2018-09-18 + +- [#522](https://github.com/stripe/stripe-php/pull/522) Fix warning when adding a new additional owner to an existing array + +## 6.17.1 - 2018-09-14 + +- [#517](https://github.com/stripe/stripe-php/pull/517) Integer-index encode all sequential arrays + +## 6.17.0 - 2018-09-05 + +- [#514](https://github.com/stripe/stripe-php/pull/514) Add support for reporting resources + +## 6.16.0 - 2018-08-23 + +- [#509](https://github.com/stripe/stripe-php/pull/509) Add support for usage record summaries + +## 6.15.0 - 2018-08-03 + +- [#504](https://github.com/stripe/stripe-php/pull/504) Add cancel support for topups + +## 6.14.0 - 2018-08-02 + +- [#505](https://github.com/stripe/stripe-php/pull/505) Add support for file links + +## 6.13.0 - 2018-07-31 + +- [#502](https://github.com/stripe/stripe-php/pull/502) Add `isDeleted()` method to `\Stripe\StripeObject` + +## 6.12.0 - 2018-07-28 + +- [#501](https://github.com/stripe/stripe-php/pull/501) Add support for scheduled query runs (`\Stripe\Sigma\ScheduledQueryRun`) for Sigma + +## 6.11.0 - 2018-07-26 + +- [#500](https://github.com/stripe/stripe-php/pull/500) Add support for Stripe Issuing + +## 6.10.4 - 2018-07-19 + +- [#498](https://github.com/stripe/stripe-php/pull/498) Internal improvements to the `\Stripe\ApiResource.classUrl()` method + +## 6.10.3 - 2018-07-16 + +- [#497](https://github.com/stripe/stripe-php/pull/497) Use HTTP/2 only for HTTPS requests + +## 6.10.2 - 2018-07-11 + +- [#494](https://github.com/stripe/stripe-php/pull/494) Enable HTTP/2 support + +## 6.10.1 - 2018-07-10 + +- [#493](https://github.com/stripe/stripe-php/pull/493) Add PHPDoc for `auto_advance` on `\Stripe\Invoice` + +## 6.10.0 - 2018-06-28 + +- [#488](https://github.com/stripe/stripe-php/pull/488) Add support for `$appPartnerId` to `Stripe::setAppInfo()` + +## 6.9.0 - 2018-06-28 + +- [#487](https://github.com/stripe/stripe-php/pull/487) Add support for payment intents + +## 6.8.2 - 2018-06-24 + +- [#486](https://github.com/stripe/stripe-php/pull/486) Make `Account.deauthorize()` return the `StripeObject` from the API + +## 6.8.1 - 2018-06-13 + +- [#472](https://github.com/stripe/stripe-php/pull/472) Added phpDoc for `ApiRequestor` and others, especially regarding thrown errors + +## 6.8.0 - 2018-06-13 + +- [#481](https://github.com/stripe/stripe-php/pull/481) Add new `\Stripe\Discount` and `\Stripe\OrderItem` classes, add more PHPDoc describing object attributes + +## 6.7.4 - 2018-05-29 + +- [#480](https://github.com/stripe/stripe-php/pull/480) PHPDoc changes for API version 2018-05-21 and the addition of the new `CHARGE_EXPIRED` event type + +## 6.7.3 - 2018-05-28 + +- [#479](https://github.com/stripe/stripe-php/pull/479) Fix unnecessary traits on `\Stripe\InvoiceLineItem` + +## 6.7.2 - 2018-05-28 + +- [#471](https://github.com/stripe/stripe-php/pull/471) Add `OBJECT_NAME` constant to all API resource classes, add `\Stripe\InvoiceLineItem` class + +## 6.7.1 - 2018-05-13 + +- [#468](https://github.com/stripe/stripe-php/pull/468) Update fields in PHP docs for accuracy + +## 6.7.0 - 2018-05-09 + +- [#466](https://github.com/stripe/stripe-php/pull/466) Add support for issuer fraud records + +## 6.6.0 - 2018-04-11 + +- [#460](https://github.com/stripe/stripe-php/pull/460) Add support for flexible billing primitives + +## 6.5.0 - 2018-04-05 + +- [#461](https://github.com/stripe/stripe-php/pull/461) Don't zero keys on non-`metadata` subobjects + +## 6.4.2 - 2018-03-17 + +- [#458](https://github.com/stripe/stripe-php/pull/458) Add PHPDoc for `account` on `\Stripe\Event` + +## 6.4.1 - 2018-03-02 + +- [#455](https://github.com/stripe/stripe-php/pull/455) Fix namespaces in PHPDoc +- [#456](https://github.com/stripe/stripe-php/pull/456) Fix namespaces for some exceptions + +## 6.4.0 - 2018-02-28 + +- [#453](https://github.com/stripe/stripe-php/pull/453) Add constants for `reason` (`REASON_*`) and `status` (`STATUS_*`) on `\Stripe\Dispute` + +## 6.3.2 - 2018-02-27 + +- [#452](https://github.com/stripe/stripe-php/pull/452) Add PHPDoc for `amount_paid` and `amount_remaining` on `\Stripe\Invoice` + +## 6.3.1 - 2018-02-26 + +- [#443](https://github.com/stripe/stripe-php/pull/443) Add event types as constants to `\Stripe\Event` class + +## 6.3.0 - 2018-02-23 + +- [#450](https://github.com/stripe/stripe-php/pull/450) Add support for `code` attribute on all Stripe exceptions + +## 6.2.0 - 2018-02-21 + +- [#440](https://github.com/stripe/stripe-php/pull/440) Add support for topups +- [#442](https://github.com/stripe/stripe-php/pull/442) Fix PHPDoc for `\Stripe\Error\SignatureVerification` + +## 6.1.0 - 2018-02-12 + +- [#435](https://github.com/stripe/stripe-php/pull/435) Fix header persistence on `Collection` objects +- [#436](https://github.com/stripe/stripe-php/pull/436) Introduce new `Idempotency` error class + +## 6.0.0 - 2018-02-07 + +Major version release. List of backwards incompatible changes to watch out for: + +- The minimum PHP version is now 5.4.0. If you're using PHP 5.3 or older, consider upgrading to a more recent version. + +* `\Stripe\AttachedObject` no longer exists. Attributes that used to be instances of `\Stripe\AttachedObject` (such as `metadata`) are now instances of `\Stripe\StripeObject`. + +- Attributes that used to be PHP arrays (such as `legal_entity->additional_owners` on `\Stripe\Account` instances) are now instances of `\Stripe\StripeObject`, except when they are empty. `\Stripe\StripeObject` has array semantics so this should not be an issue unless you are actively checking types. + +* `\Stripe\Collection` now derives from `\Stripe\StripeObject` rather than from `\Stripe\ApiResource`. + +Pull requests included in this release: + +- [#410](https://github.com/stripe/stripe-php/pull/410) Drop support for PHP 5.3 +- [#411](https://github.com/stripe/stripe-php/pull/411) Use traits for common API operations +- [#414](https://github.com/stripe/stripe-php/pull/414) Use short array syntax +- [#404](https://github.com/stripe/stripe-php/pull/404) Fix serialization logic +- [#417](https://github.com/stripe/stripe-php/pull/417) Remove `ExternalAccount` class +- [#418](https://github.com/stripe/stripe-php/pull/418) Increase test coverage +- [#421](https://github.com/stripe/stripe-php/pull/421) Update CA bundle and add script for future updates +- [#422](https://github.com/stripe/stripe-php/pull/422) Use vendored CA bundle for all requests +- [#428](https://github.com/stripe/stripe-php/pull/428) Support for automatic request retries + +## 5.9.2 - 2018-02-07 + +- [#431](https://github.com/stripe/stripe-php/pull/431) Update PHPDoc @property tags for latest API version + +## 5.9.1 - 2018-02-06 + +- [#427](https://github.com/stripe/stripe-php/pull/427) Add and update PHPDoc @property tags on all API resources + +## 5.9.0 - 2018-01-17 + +- [#421](https://github.com/stripe/stripe-php/pull/421) Updated bundled CA certificates +- [#423](https://github.com/stripe/stripe-php/pull/423) Escape unsanitized input in OAuth example + +## 5.8.0 - 2017-12-20 + +- [#403](https://github.com/stripe/stripe-php/pull/403) Add `__debugInfo()` magic method to `StripeObject` + +## 5.7.0 - 2017-11-28 + +- [#390](https://github.com/stripe/stripe-php/pull/390) Remove some unsupported API methods +- [#391](https://github.com/stripe/stripe-php/pull/391) Alphabetize the list of API resources in `Util::convertToStripeObject()` and add missing resources +- [#393](https://github.com/stripe/stripe-php/pull/393) Fix expiry date update for card sources + +## 5.6.0 - 2017-10-31 + +- [#386](https://github.com/stripe/stripe-php/pull/386) Support for exchange rates APIs + +## 5.5.1 - 2017-10-30 + +- [#387](https://github.com/stripe/stripe-php/pull/387) Allow `personal_address_kana` and `personal_address_kanji` to be updated on an account + +## 5.5.0 - 2017-10-27 + +- [#385](https://github.com/stripe/stripe-php/pull/385) Support for listing source transactions + +## 5.4.0 - 2017-10-24 + +- [#383](https://github.com/stripe/stripe-php/pull/383) Add static methods to manipulate resources from parent + - `Account` gains methods for external accounts and login links (e.g. `createExternalAccount`, `createLoginLink`) + - `ApplicationFee` gains methods for refunds + - `Customer` gains methods for sources + - `Transfer` gains methods for reversals + +## 5.3.0 - 2017-10-11 + +- [#378](https://github.com/stripe/stripe-php/pull/378) Rename source `delete` to `detach` (and deprecate the former) + +## 5.2.3 - 2017-09-27 + +- Add PHPDoc for `Card` + +## 5.2.2 - 2017-09-20 + +- Fix deserialization mapping of `FileUpload` objects + +## 5.2.1 - 2017-09-14 + +- Serialized `shipping` nested attribute + +## 5.2.0 - 2017-08-29 + +- Add support for `InvalidClient` OAuth error + +## 5.1.3 - 2017-08-14 + +- Allow `address_kana` and `address_kanji` to be updated for custom accounts + +## 5.1.2 - 2017-08-01 + +- Fix documented return type of `autoPagingIterator()` (was missing namespace) + +## 5.1.1 - 2017-07-03 + +- Fix order returns to use the right URL `/v1/order_returns` + +## 5.1.0 - 2017-06-30 + +- Add support for OAuth + +## 5.0.0 - 2017-06-27 + +- `pay` on invoice now takes params as well as opts + +## 4.13.0 - 2017-06-19 + +- Add support for ephemeral keys + +## 4.12.0 - 2017-06-05 + +- Clients can implement `getUserAgentInfo()` to add additional user agent information + +## 4.11.0 - 2017-06-05 + +- Implement `Countable` for `AttachedObject` (`metadata` and `additional_owners`) + +## 4.10.0 - 2017-05-25 + +- Add support for login links + +## 4.9.1 - 2017-05-10 + +- Fix docs to include arrays on `$id` parameter for retrieve methods + +## 4.9.0 - 2017-04-28 + +- Support for checking webhook signatures + +## 4.8.1 - 2017-04-24 + +- Allow nested field `payout_schedule` to be updated + +## 4.8.0 - 2017-04-20 + +- Add `\Stripe\Stripe::setLogger()` to support an external PSR-3 compatible logger + +## 4.7.0 - 2017-04-10 + +- Add support for payouts and recipient transfers + +## 4.6.0 - 2017-04-06 + +- Please see 4.7.0 instead (no-op release) + +## 4.5.1 - 2017-03-22 + +- Remove hard dependency on cURL + +## 4.5.0 - 2017-03-20 + +- Support for detaching sources from customers + +## 4.4.2 - 2017-02-27 + +- Correct handling of `owner` parameter when updating sources + +## 4.4.1 - 2017-02-24 + +- Correct the error check on a bad JSON decoding + +## 4.4.0 - 2017-01-18 + +- Add support for updating sources + +## 4.3.0 - 2016-11-30 + +- Add support for verifying sources + +## 4.2.0 - 2016-11-21 + +- Add retrieve method for 3-D Secure resources + +## 4.1.1 - 2016-10-21 + +- Add docblock with model properties for `Plan` + +## 4.1.0 - 2016-10-18 + +- Support for 403 status codes (permission denied) + +## 4.0.1 - 2016-10-17 + +- Fix transfer reversal materialization +- Fixes for some property definitions in docblocks + +## 4.0.0 - 2016-09-28 + +- Support for subscription items +- Drop attempt to force TLS 1.2: please note that this could be breaking if you're using old OS distributions or packages and upgraded recently (so please make sure to test your integration!) + +## 3.23.0 - 2016-09-15 + +- Add support for Apple Pay domains + +## 3.22.0 - 2016-09-13 + +- Add `Stripe::setAppInfo` to allow plugins to register user agent information + +## 3.21.0 - 2016-08-25 + +- Add `Source` model for generic payment sources + +## 3.20.0 - 2016-08-08 + +- Add `getDeclineCode` to card errors + +## 3.19.0 - 2016-07-29 + +- Opt requests directly into TLS 1.2 where OpenSSL >= 1.0.1 (see #277 for context) + +## 3.18.0 - 2016-07-28 + +- Add new `STATUS_` constants for subscriptions + +## 3.17.1 - 2016-07-28 + +- Fix auto-paging iterator so that it plays nicely with `iterator_to_array` + +## 3.17.0 - 2016-07-14 + +- Add field annotations to model classes for better editor hinting + +## 3.16.0 - 2016-07-12 + +- Add `ThreeDSecure` model for 3-D secure payments + +## 3.15.0 - 2016-06-29 + +- Add static `update` method to all resources that can be changed. + +## 3.14.3 - 2016-06-20 + +- Make sure that cURL never sends `Expects: 100-continue`, even on large request bodies + +## 3.14.2 - 2016-06-03 + +- Add `inventory` under `SKU` to list of keys that have nested data and can be updated + +## 3.14.1 - 2016-05-27 + +- Fix some inconsistencies in PHPDoc + +## 3.14.0 - 2016-05-25 + +- Add support for returning Relay orders + +## 3.13.0 - 2016-05-04 + +- Add `list`, `create`, `update`, `retrieve`, and `delete` methods to the Subscription class + +## 3.12.1 - 2016-04-07 + +- Additional check on value arrays for some extra safety + +## 3.12.0 - 2016-03-31 + +- Fix bug `refreshFrom` on `StripeObject` would not take an `$opts` array +- Fix bug where `$opts` not passed to parent `save` method in `Account` +- Fix bug where non-existent variable was referenced in `reverse` in `Transfer` +- Update CA cert bundle for compatibility with OpenSSL versions below 1.0.1 + +## 3.11.0 - 2016-03-22 + +- Allow `CurlClient` to be initialized with default `CURLOPT_*` options + +## 3.10.1 - 2016-03-22 + +- Fix bug where request params and options were ignored in `ApplicationFee`'s `refund.` + +## 3.10.0 - 2016-03-15 + +- Add `reject` on `Account` to support the new API feature + +## 3.9.2 - 2016-03-04 + +- Fix error when an object's metadata is set more than once + +## 3.9.1 - 2016-02-24 + +- Fix encoding behavior of nested arrays for requests (see #227) + +## 3.9.0 - 2016-02-09 + +- Add automatic pagination mechanism with `autoPagingIterator()` +- Allow global account ID to be set with `Stripe::setAccountId()` + +## 3.8.0 - 2016-02-08 + +- Add `CountrySpec` model for looking up country payment information + +## 3.7.1 - 2016-02-01 + +- Update bundled CA certs + +## 3.7.0 - 2016-01-27 + +- Support deleting Relay products and SKUs + +## 3.6.0 - 2016-01-05 + +- Allow configuration of HTTP client timeouts + +## 3.5.0 - 2015-12-01 + +- Add a verification routine for external accounts + +## 3.4.0 - 2015-09-14 + +- Products, SKUs, and Orders -- https://stripe.com/relay + +## 3.3.0 - 2015-09-11 + +- Add support for 429 Rate Limit response + +## 3.2.0 - 2015-08-17 + +- Add refund listing and retrieval without an associated charge + +## 3.1.0 - 2015-08-03 + +- Add dispute listing and retrieval +- Add support for manage account deletion + +## 3.0.0 - 2015-07-28 + +- Rename `\Stripe\Object` to `\Stripe\StripeObject` (PHP 7 compatibility) +- Rename `getCode` and `getParam` in exceptions to `getStripeCode` and `getStripeParam` +- Add support for calling `json_encode` on Stripe objects in PHP 5.4+ +- Start supporting/testing PHP 7 + +## 2.3.0 - 2015-07-06 + +- Add request ID to all Stripe exceptions + +## 2.2.0 - 2015-06-01 + +- Add support for Alipay accounts as sources +- Add support for bank accounts as sources (private beta) +- Add support for bank accounts and cards as external_accounts on Account objects + +## 2.1.4 - 2015-05-13 + +- Fix CA certificate file path (thanks @lphilps & @matthewarkin) + +## 2.1.3 - 2015-05-12 + +- Fix to account updating to permit `tos_acceptance` and `personal_address` to be set properly +- Fix to Transfer reversal creation (thanks @neatness!) +- Network requests are now done through a swappable class for easier mocking + +## 2.1.2 - 2015-04-10 + +- Remove SSL cert revokation checking (all pre-Heartbleed certs have expired) +- Bug fixes to account updating + +## 2.1.1 - 2015-02-27 + +- Support transfer reversals + +## 2.1.0 - 2015-02-19 + +- Support new API version (2015-02-18) +- Added Bitcoin Receiever update and delete actions +- Edited tests to prefer "source" over "card" as per new API version + +## 2.0.1 - 2015-02-16 + +- Fix to fetching endpoints that use a non-default baseUrl (`FileUpload`) + +## 2.0.0 - 2015-02-14 + +- Bumped minimum version to 5.3.3 +- Switched to Stripe namespace instead of Stripe\_ class name prefiexes (thanks @chadicus!) +- Switched tests to PHPUnit (thanks @chadicus!) +- Switched style guide to PSR2 (thanks @chadicus!) +- Added \$opts hash to the end of most methods: this permits passing 'idempotency_key', 'stripe_account', or 'stripe_version'. The last 2 will persist across multiple object loads. +- Added support for retrieving Account by ID + +## 1.18.0 - 2015-01-21 + +- Support making bitcoin charges through BitcoinReceiver source object + +## 1.17.5 - 2014-12-23 + +- Adding support for creating file uploads. + +## 1.17.4 - 2014-12-15 + +- Saving objects fetched with a custom key now works (thanks @JustinHook & @jpasilan) +- Added methods for reporting charges as safe or fraudulent and for specifying the reason for refunds + +## 1.17.3 - 2014-11-06 + +- Better handling of HHVM support for SSL certificate blacklist checking. + +## 1.17.2 - 2014-09-23 + +- Coupons now are backed by a `Stripe_Coupon` instead of `Stripe_Object`, and support updating metadata +- Running operations (`create`, `retrieve`, `all`) on upcoming invoice items now works + +## 1.17.1 - 2014-07-31 + +- Requests now send Content-Type header + +## 1.17.0 - 2014-07-29 + +- Application Fee refunds now a list instead of array +- HHVM now works +- Small bug fixes (thanks @bencromwell & @fastest963) +- `__toString` now returns the name of the object in addition to its JSON representation + +## 1.16.0 - 2014-06-17 + +- Add metadata for refunds and disputes + +## 1.15.0 - 2014-05-28 + +- Support canceling transfers + +## 1.14.1 - 2014-05-21 + +- Support cards for recipients. + +## 1.13.1 - 2014-05-15 + +- Fix bug in account resource where `id` wasn't in the result + +## 1.13.0 - 2014-04-10 + +- Add support for certificate blacklisting +- Update ca bundle +- Drop support for HHVM (Temporarily) + +## 1.12.0 - 2014-04-01 + +- Add Stripe_RateLimitError for catching rate limit errors. +- Update to Zend coding style (thanks, @jpiasetz) + +## 1.11.0 - 2014-01-29 + +- Add support for multiple subscriptions per customer + +## 1.10.1 - 2013-12-02 + +- Add new ApplicationFee + +## 1.9.1 - 2013-11-08 + +- Fix a bug where a null nestable object causes warnings to fire. + +## 1.9.0 - 2013-10-16 + +- Add support for metadata API. + +## 1.8.4 - 2013-09-18 + +- Add support for closing disputes. + +## 1.8.3 - 2013-08-13 + +- Add new Balance and BalanceTransaction + +## 1.8.2 - 2013-08-12 + +- Add support for unsetting attributes by updating to NULL. Setting properties to a blank string is now an error. + +## 1.8.1 - 2013-07-12 + +- Add support for multiple cards API (Stripe API version 2013-07-12: https://stripe.com/docs/upgrades#2013-07-05) + +## 1.8.0 - 2013-04-11 + +- Allow Transfers to be creatable +- Add new Recipient resource + +## 1.7.15 - 2013-02-21 + +- Add 'id' to the list of permanent object attributes + +## 1.7.14 - 2013-02-20 + +- Don't re-encode strings that are already encoded in UTF-8. If you were previously using plan or coupon objects with UTF-8 IDs, they may have been treated as ISO-8859-1 (Latin-1) and encoded to UTF-8 a 2nd time. You may now need to pass the IDs to utf8_encode before passing them to Stripe_Plan::retrieve or Stripe_Coupon::retrieve. +- Ensure that all input is encoded in UTF-8 before submitting it to Stripe's servers. (github issue #27) + +## 1.7.13 - 2013-02-01 + +- Add support for passing options when retrieving Stripe objects e.g., Stripe_Charge::retrieve(array("id"=>"foo", "expand" => array("customer"))); Stripe_Charge::retrieve("foo") will continue to work + +## 1.7.12 - 2013-01-15 + +- Add support for setting a Stripe API version override + +## 1.7.11 - 2012-12-30 + +- Version bump to cleanup constants and such (fix issue #26) + +## 1.7.10 - 2012-11-08 + +- Add support for updating charge disputes. +- Fix bug preventing retrieval of null attributes + +## 1.7.9 - 2012-11-08 + +- Fix usage under autoloaders such as the one generated by composer (fix issue #22) + +## 1.7.8 - 2012-10-30 + +- Add support for creating invoices. +- Add support for new invoice lines return format +- Add support for new list objects + +## 1.7.7 - 2012-09-14 + +- Get all of the various version numbers in the repo in sync (no other changes) + +## 1.7.6 - 2012-08-31 + +- Add update and pay methods to Invoice resource + +## 1.7.5 - 2012-08-23 + +- Change internal function names so that Stripe_SingletonApiRequest is E_STRICT-clean (github issue #16) + +## 1.7.4 - 2012-08-21 + +- Bugfix so that Stripe objects (e.g. Customer, Charge objects) used in API calls are transparently converted to their object IDs + +## 1.7.3 - 2012-08-15 + +- Add new Account resource + +## 1.7.2 - 2012-06-26 + +- Make clearer that you should be including lib/Stripe.php, not test/Stripe.php (github issue #14) + +## 1.7.1 - 2012-05-24 + +- Add missing argument to Stripe_InvalidRequestError constructor in Stripe_ApiResource::instanceUrl. Fixes a warning when Stripe_ApiResource::instanceUrl is called on a resource with no ID (fix issue #12) + +## 1.7.0 - 2012-05-17 + +- Support Composer and Packagist (github issue #9) +- Add new deleteDiscount method to Stripe_Customer +- Add new Transfer resource +- Switch from using HTTP Basic auth to Bearer auth. (Note: Stripe will support Basic auth for the indefinite future, but recommends Bearer auth when possible going forward) +- Numerous test suite improvements diff --git a/libraries/Stripe/LICENSE b/libraries/Stripe/LICENSE new file mode 100644 index 00000000000..847c705ad35 --- /dev/null +++ b/libraries/Stripe/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2010-2019 Stripe, Inc. (https://stripe.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libraries/Stripe/OPENAPI_VERSION b/libraries/Stripe/OPENAPI_VERSION new file mode 100644 index 00000000000..5f5b311191b --- /dev/null +++ b/libraries/Stripe/OPENAPI_VERSION @@ -0,0 +1 @@ +v1267 \ No newline at end of file diff --git a/libraries/Stripe/README.md b/libraries/Stripe/README.md new file mode 100644 index 00000000000..436aef2cb6d --- /dev/null +++ b/libraries/Stripe/README.md @@ -0,0 +1,298 @@ +# Stripe PHP bindings + +[![Build Status](https://github.com/stripe/stripe-php/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/stripe/stripe-php/actions?query=branch%3Amaster) +[![Latest Stable Version](https://poser.pugx.org/stripe/stripe-php/v/stable.svg)](https://packagist.org/packages/stripe/stripe-php) +[![Total Downloads](https://poser.pugx.org/stripe/stripe-php/downloads.svg)](https://packagist.org/packages/stripe/stripe-php) +[![License](https://poser.pugx.org/stripe/stripe-php/license.svg)](https://packagist.org/packages/stripe/stripe-php) +[![Code Coverage](https://coveralls.io/repos/stripe/stripe-php/badge.svg?branch=master)](https://coveralls.io/r/stripe/stripe-php?branch=master) + +The Stripe PHP library provides convenient access to the Stripe API from +applications written in the PHP language. It includes a pre-defined set of +classes for API resources that initialize themselves dynamically from API +responses which makes it compatible with a wide range of versions of the Stripe +API. + +## Requirements + +PHP 5.6.0 and later. + +## Composer + +You can install the bindings via [Composer](http://getcomposer.org/). Run the following command: + +```bash +composer require stripe/stripe-php +``` + +To use the bindings, use Composer's [autoload](https://getcomposer.org/doc/01-basic-usage.md#autoloading): + +```php +require_once 'vendor/autoload.php'; +``` + +## Manual Installation + +If you do not wish to use Composer, you can download the [latest release](https://github.com/stripe/stripe-php/releases). Then, to use the bindings, include the `init.php` file. + +```php +require_once '/path/to/stripe-php/init.php'; +``` + +## Dependencies + +The bindings require the following extensions in order to work properly: + +- [`curl`](https://secure.php.net/manual/en/book.curl.php), although you can use your own non-cURL client if you prefer +- [`json`](https://secure.php.net/manual/en/book.json.php) +- [`mbstring`](https://secure.php.net/manual/en/book.mbstring.php) (Multibyte String) + +If you use Composer, these dependencies should be handled automatically. If you install manually, you'll want to make sure that these extensions are available. + +## Getting Started + +Simple usage looks like: + +```php +$stripe = new \Stripe\StripeClient('sk_test_BQokikJOvBiI2HlWgH4olfQ2'); +$customer = $stripe->customers->create([ + 'description' => 'example customer', + 'email' => 'email@example.com', + 'payment_method' => 'pm_card_visa', +]); +echo $customer; +``` + +### Client/service patterns vs legacy patterns + +You can continue to use the legacy integration patterns used prior to version [7.33.0](https://github.com/stripe/stripe-php/blob/master/CHANGELOG.md#7330---2020-05-14). Review the [migration guide](https://github.com/stripe/stripe-php/wiki/Migration-to-StripeClient-and-services-in-7.33.0) for the backwards-compatible client/services pattern changes. + +## Documentation + +See the [PHP API docs](https://stripe.com/docs/api/?lang=php#intro). + +See [video demonstrations][youtube-playlist] covering how to use the library. + +## Legacy Version Support + +### PHP 5.4 & 5.5 + +If you are using PHP 5.4 or 5.5, you should consider upgrading your environment as those versions have been past end of life since September 2015 and July 2016 respectively. +Otherwise, you can still use Stripe by downloading stripe-php v6.43.1 ([zip](https://github.com/stripe/stripe-php/archive/v6.43.1.zip), [tar.gz](https://github.com/stripe/stripe-php/archive/6.43.1.tar.gz)) from our [releases page](https://github.com/stripe/stripe-php/releases). This version will work but might not support recent features we added since the version was released and upgrading PHP is the best course of action. + +### PHP 5.3 + +If you are using PHP 5.3, you should upgrade your environment as this version has been past end of life since August 2014. +Otherwise, you can download v5.9.2 ([zip](https://github.com/stripe/stripe-php/archive/v5.9.2.zip), [tar.gz](https://github.com/stripe/stripe-php/archive/v5.9.2.tar.gz)) from our [releases page](https://github.com/stripe/stripe-php/releases). This version will continue to work with new versions of the Stripe API for all common uses. + +## Custom Request Timeouts + +> **Note** +> We do not recommend decreasing the timeout for non-read-only calls (e.g. charge creation), since even if you locally timeout, the request on Stripe's side can still complete. If you are decreasing timeouts on these calls, make sure to use [idempotency tokens](https://stripe.com/docs/api/?lang=php#idempotent_requests) to avoid executing the same transaction twice as a result of timeout retry logic. + +To modify request timeouts (connect or total, in seconds) you'll need to tell the API client to use a CurlClient other than its default. You'll set the timeouts in that CurlClient. + +```php +// set up your tweaked Curl client +$curl = new \Stripe\HttpClient\CurlClient(); +$curl->setTimeout(10); // default is \Stripe\HttpClient\CurlClient::DEFAULT_TIMEOUT +$curl->setConnectTimeout(5); // default is \Stripe\HttpClient\CurlClient::DEFAULT_CONNECT_TIMEOUT + +echo $curl->getTimeout(); // 10 +echo $curl->getConnectTimeout(); // 5 + +// tell Stripe to use the tweaked client +\Stripe\ApiRequestor::setHttpClient($curl); + +// use the Stripe API client as you normally would +``` + +## Custom cURL Options (e.g. proxies) + +Need to set a proxy for your requests? Pass in the requisite `CURLOPT_*` array to the CurlClient constructor, using the same syntax as `curl_stopt_array()`. This will set the default cURL options for each HTTP request made by the SDK, though many more common options (e.g. timeouts; see above on how to set those) will be overridden by the client even if set here. + +```php +// set up your tweaked Curl client +$curl = new \Stripe\HttpClient\CurlClient([CURLOPT_PROXY => 'proxy.local:80']); +// tell Stripe to use the tweaked client +\Stripe\ApiRequestor::setHttpClient($curl); +``` + +Alternately, a callable can be passed to the CurlClient constructor that returns the above array based on request inputs. See `testDefaultOptions()` in `tests/CurlClientTest.php` for an example of this behavior. Note that the callable is called at the beginning of every API request, before the request is sent. + +### Configuring a Logger + +The library does minimal logging, but it can be configured +with a [`PSR-3` compatible logger][psr3] so that messages +end up there instead of `error_log`: + +```php +\Stripe\Stripe::setLogger($logger); +``` + +### Accessing response data + +You can access the data from the last API response on any object via `getLastResponse()`. + +```php +$customer = $stripe->customers->create([ + 'description' => 'example customer', +]); +echo $customer->getLastResponse()->headers['Request-Id']; +``` + +### SSL / TLS compatibility issues + +Stripe's API now requires that [all connections use TLS 1.2](https://stripe.com/blog/upgrading-tls). Some systems (most notably some older CentOS and RHEL versions) are capable of using TLS 1.2 but will use TLS 1.0 or 1.1 by default. In this case, you'd get an `invalid_request_error` with the following error message: "Stripe no longer supports API requests made with TLS 1.0. Please initiate HTTPS connections with TLS 1.2 or later. You can learn more about this at [https://stripe.com/blog/upgrading-tls](https://stripe.com/blog/upgrading-tls).". + +The recommended course of action is to [upgrade your cURL and OpenSSL packages](https://support.stripe.com/questions/how-do-i-upgrade-my-stripe-integration-from-tls-1-0-to-tls-1-2#php) so that TLS 1.2 is used by default, but if that is not possible, you might be able to solve the issue by setting the `CURLOPT_SSLVERSION` option to either `CURL_SSLVERSION_TLSv1` or `CURL_SSLVERSION_TLSv1_2`: + +```php +$curl = new \Stripe\HttpClient\CurlClient([CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1]); +\Stripe\ApiRequestor::setHttpClient($curl); +``` + +### Per-request Configuration + +For apps that need to use multiple keys during the lifetime of a process, like +one that uses [Stripe Connect][connect], it's also possible to set a +per-request key and/or account: + +```php +$customers = $stripe->customers->all([],[ + 'api_key' => 'sk_test_...', + 'stripe_account' => 'acct_...' +]); + +$stripe->customers->retrieve('cus_123456789', [], [ + 'api_key' => 'sk_test_...', + 'stripe_account' => 'acct_...' +]); +``` + +### Configuring CA Bundles + +By default, the library will use its own internal bundle of known CA +certificates, but it's possible to configure your own: + +```php +\Stripe\Stripe::setCABundlePath("path/to/ca/bundle"); +``` + +### Configuring Automatic Retries + +The library can be configured to automatically retry requests that fail due to +an intermittent network problem: + +```php +\Stripe\Stripe::setMaxNetworkRetries(2); +``` + +[Idempotency keys][idempotency-keys] are added to requests to guarantee that +retries are safe. + +### Telemetry + +By default, the library sends telemetry to Stripe regarding request latency and feature usage. These +numbers help Stripe improve the overall latency of its API for all users, and +improve popular features. + +You can disable this behavior if you prefer: + +```php +\Stripe\Stripe::setEnableTelemetry(false); +``` + +### Beta SDKs + +Stripe has features in the beta phase that can be accessed via the beta version of this package. +We would love for you to try these and share feedback with us before these features reach the stable phase. +Use the `composer require` command with an exact version specified to install the beta version of the stripe-php pacakge. + +```bash +composer require stripe/stripe-php:v9.2.0-beta.1 +``` + +> **Note** +> There can be breaking changes between beta versions. Therefore we recommend pinning the package version to a specific beta version in your composer.json file. This way you can install the same version each time without breaking changes unless you are intentionally looking for the latest beta version. + +We highly recommend keeping an eye on when the beta feature you are interested in goes from beta to stable so that you can move from using a beta version of the SDK to the stable version. + +If your beta feature requires a `Stripe-Version` header to be sent, set the `apiVersion` property of `config` object by using the function `addBetaVersion`: + +```php +Stripe::addBetaVersion("feature_beta", "v3"); +``` + +## Support + +New features and bug fixes are released on the latest major version of the Stripe PHP library. If you are on an older major version, we recommend that you upgrade to the latest in order to use the new features and bug fixes including those for security vulnerabilities. Older major versions of the package will continue to be available for use, but will not be receiving any updates. + +## Development + +Get [Composer][composer]. For example, on Mac OS: + +```bash +brew install composer +``` + +Install dependencies: + +```bash +composer install +``` + +The test suite depends on [stripe-mock], so make sure to fetch and run it from a +background terminal ([stripe-mock's README][stripe-mock] also contains +instructions for installing via Homebrew and other methods): + +```bash +go install github.com/stripe/stripe-mock@latest +stripe-mock +``` + +Install dependencies as mentioned above (which will resolve [PHPUnit](http://packagist.org/packages/phpunit/phpunit)), then you can run the test suite: + +```bash +./vendor/bin/phpunit +``` + +Or to run an individual test file: + +```bash +./vendor/bin/phpunit tests/Stripe/UtilTest.php +``` + +Update bundled CA certificates from the [Mozilla cURL release][curl]: + +```bash +./update_certs.php +``` + +The library uses [PHP CS Fixer][php-cs-fixer] for code formatting. Code must be formatted before PRs are submitted, otherwise CI will fail. Run the formatter with: + +```bash +./vendor/bin/php-cs-fixer fix -v . +``` + +## Attention plugin developers + +Are you writing a plugin that integrates Stripe and embeds our library? Then please use the `setAppInfo` function to identify your plugin. For example: + +```php +\Stripe\Stripe::setAppInfo("MyAwesomePlugin", "1.2.34", "https://myawesomeplugin.info"); +``` + +The method should be called once, before any request is sent to the API. The second and third parameters are optional. + +### SSL / TLS configuration option + +See the "SSL / TLS compatibility issues" paragraph above for full context. If you want to ensure that your plugin can be used on all systems, you should add a configuration option to let your users choose between different values for `CURLOPT_SSLVERSION`: none (default), `CURL_SSLVERSION_TLSv1` and `CURL_SSLVERSION_TLSv1_2`. + +[composer]: https://getcomposer.org/ +[connect]: https://stripe.com/connect +[curl]: http://curl.haxx.se/docs/caextract.html +[idempotency-keys]: https://stripe.com/docs/api/?lang=php#idempotent_requests +[php-cs-fixer]: https://github.com/FriendsOfPHP/PHP-CS-Fixer +[psr3]: http://www.php-fig.org/psr/psr-3/ +[stripe-mock]: https://github.com/stripe/stripe-mock +[youtube-playlist]: https://www.youtube.com/playlist?list=PLy1nL-pvL2M6cUbiHrfMkXxZ9j9SGBxFE diff --git a/libraries/Stripe/VERSION b/libraries/Stripe/VERSION new file mode 100644 index 00000000000..9f7cbe79bd3 --- /dev/null +++ b/libraries/Stripe/VERSION @@ -0,0 +1 @@ +15.10.0 diff --git a/libraries/Stripe/composer.json b/libraries/Stripe/composer.json new file mode 100644 index 00000000000..1e4dd7cbb10 --- /dev/null +++ b/libraries/Stripe/composer.json @@ -0,0 +1,46 @@ +{ + "name": "stripe/stripe-php", + "description": "Stripe PHP Library", + "keywords": [ + "stripe", + "payment processing", + "api" + ], + "homepage": "https://stripe.com/", + "license": "MIT", + "authors": [ + { + "name": "Stripe and contributors", + "homepage": "https://github.com/stripe/stripe-php/contributors" + } + ], + "require": { + "php": ">=5.6.0", + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.2" + }, + "autoload": { + "psr-4": { + "Stripe\\": "lib/" + } + }, + "autoload-dev": { + "psr-4": { + "Stripe\\": [ + "tests/", + "tests/Stripe/" + ] + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/libraries/Stripe/data/ca-certificates.crt b/libraries/Stripe/data/ca-certificates.crt new file mode 100644 index 00000000000..26f135040c2 --- /dev/null +++ b/libraries/Stripe/data/ca-certificates.crt @@ -0,0 +1,3347 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Tue Apr 26 03:12:05 2022 GMT +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.29. +## SHA256: 34a54d5191775c1bd37be6cfd3f09e831e072555dc3a2e51f4a2c4b0f8ada5cc +## + + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +Network Solutions Certificate Authority +======================================= +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr +IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx +MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx +jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT +aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT +crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc +/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB +AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv +bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q +4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ +GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD +ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2011 +======================================================= +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT +O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y +aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT +AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo +IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI +1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa +71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u +8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH +3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 +MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu +b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt +XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD +/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N +7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +Staat der Nederlanden EV Root CA +================================ +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M +MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl +cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk +SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW +O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r +0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8 +Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV +XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr +08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV +0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd +74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx +fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa +ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu +c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq +5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN +b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN +f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi +5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4 +WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK +DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy +eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg== +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +TrustCor RootCert CA-1 +====================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYDVQQGEwJQQTEP +MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig +U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkx +MjMxMTcyMzE2WjCBpDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFu +YW1hIENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUGA1UECwwe +VHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZUcnVzdENvciBSb290Q2Vy +dCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv463leLCJhJrMxnHQFgKq1mq +jQCj/IDHUHuO1CAmujIS2CNUSSUQIpidRtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4 +pQa81QBeCQryJ3pS/C3Vseq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0 +JEsq1pme9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CVEY4h +gLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorWhnAbJN7+KIor0Gqw +/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/DeOxCbeKyKsZn3MzUOcwHwYDVR0j +BBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwDQYJKoZIhvcNAQELBQADggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5 +mDo4Nvu7Zp5I/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZyonnMlo2HD6C +qFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djtsL1Ac59v2Z3kf9YKVmgenFK+P +3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdNzl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +TrustCor RootCert CA-2 +====================== +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNVBAYTAlBBMQ8w +DQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQwIgYDVQQKDBtUcnVzdENvciBT +eXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0 +eTEfMB0GA1UEAwwWVHJ1c3RDb3IgUm9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEy +MzExNzI2MzlaMIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5h +bWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0 +IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnIG7CKqJiJJWQdsg4foDSq8Gb +ZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9Nk +RvRUqdw6VC0xK5mC8tkq1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1 +oYxOdqHp2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nKDOOb +XUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hapeaz6LMvYHL1cEksr1 +/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF3wP+TfSvPd9cW436cOGlfifHhi5q +jxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQP +eSghYA2FFn3XVDjxklb9tTNMg9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+Ctg +rKAmrhQhJ8Z3mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAdBgNVHQ4EFgQU +2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6UnrybPZx9mCAZ5YwwYrIwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/h +Osh80QA9z+LqBrWyOrsGS2h60COXdKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnp +kpfbsEZC89NiqpX+MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv +2wnL/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RXCI/hOWB3 +S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYaZH9bDTMJBzN7Bj8RpFxw +PIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dv +DDqPys/cA8GiCcjl/YBeyGBCARsaU1q7N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYU +RpFHmygk71dSTlxCnKr3Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANE +xdqtvArBAs8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp5KeX +RKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu1uwJ +-----END CERTIFICATE----- + +TrustCor ECA-1 +============== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYDVQQGEwJQQTEP +MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig +U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3Mjgw +N1owgZwxCzAJBgNVBAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5 +MSQwIgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29y +IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3IgRUNBLTEwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb3w9U73NjKYKtR8aja+3+XzP4Q1HpGjOR +MRegdMTUpwHmspI+ap3tDvl0mEDTPwOABoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23 +xFUfJ3zSCNV2HykVh0A53ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmc +p0yJF4OuowReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/wZ0+ +fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZFZtS6mFjBAgMBAAGj +YzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAfBgNVHSMEGDAWgBREnkj1zG1I1KBL +f/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF +AAOCAQEABT41XBVwm8nHc2FvcivUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u +/ukZMjgDfxT2AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50soIipX1TH0Xs +J5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BIWJZpTdwHjFGTot+fDz2LYLSC +jaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1WitJ/X5g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G4 +========================================= +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu +bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT +AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D +umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV +3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds +8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ +e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 +ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X +xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV +7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW +Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n +MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q +jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht +7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK +YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt +jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ +m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW +RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA +JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G ++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT +kcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 +zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf +LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq +stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o +WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ +OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 +Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE +uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm ++9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj +ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H +PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H +ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla +4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R +vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd +zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O +856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH +Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu +3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 +NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv +bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg +Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw +MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw +EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz +BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv +T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv +B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse +zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM +VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j +7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z +JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe +8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO +Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ +UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx +j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt +dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM +5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb +5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 +EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H +hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy +g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 +r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG +EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew +HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY +QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p +fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 +HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 +fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt +g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 +NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk +fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ +P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY +njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK +HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL +LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s +ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K +h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 +CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA +4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo +WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj +6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT +OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck +bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG +A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj +dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw +NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD +ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz +2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b +bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 +NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd +gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW +VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f +Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ +juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas +DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS +VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI +04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl +0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd +Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY +YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp +adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x +xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP +jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM +MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z +ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r +AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl +e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb +a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS ++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M +kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG +r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q +S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV +J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL +dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD +ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh +swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel +/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn +jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 +9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M +7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 +0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR +WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW +HYbL +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- diff --git a/libraries/Stripe/init.php b/libraries/Stripe/init.php new file mode 100644 index 00000000000..cb52782b950 --- /dev/null +++ b/libraries/Stripe/init.php @@ -0,0 +1,366 @@ +controller.requirement_collection + * is application, which includes Custom accounts, the properties below are always + * returned. + * + * For accounts where controller.requirement_collection + * is stripe, which includes Standard and Express accounts, some properties are only returned + * until you create an Account Link or Account Session + * to start Connect Onboarding. Learn about the differences between accounts. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $business_profile Business information about the account. + * @property null|string $business_type The business type. After you create an Account Link or Account Session, this property is only returned for accounts where controller.requirement_collection is application, which includes Custom accounts. + * @property null|\EDD\Vendor\Stripe\StripeObject $capabilities + * @property null|bool $charges_enabled Whether the account can create live charges. + * @property null|\EDD\Vendor\Stripe\StripeObject $company + * @property null|\EDD\Vendor\Stripe\StripeObject $controller + * @property null|string $country The account's country. + * @property null|int $created Time at which the account was connected. Measured in seconds since the Unix epoch. + * @property null|string $default_currency Three-letter ISO currency code representing the default currency for the account. This must be a currency that EDD\Vendor\Stripe supports in the account's country. + * @property null|bool $details_submitted Whether account details have been submitted. Accounts with EDD\Vendor\Stripe Dashboard access, which includes Standard accounts, cannot receive payouts before this is true. Accounts where this is false should be directed to an onboarding flow to finish submitting account details. + * @property null|string $email An email address associated with the account. It's not used for authentication and EDD\Vendor\Stripe doesn't market to this field without explicit approval from the platform. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card> $external_accounts External accounts (bank accounts and debit cards) currently attached to this account. External accounts are only returned for requests where controller[is_controller] is true. + * @property null|\EDD\Vendor\Stripe\StripeObject $future_requirements + * @property null|\EDD\Vendor\Stripe\Person $individual

    This is an object representing a person associated with a EDD\Vendor\Stripe account.

    A platform cannot access a person for an account where account.controller.requirement_collection is stripe, which includes Standard and Express accounts, after creating an Account Link or Account Session to start Connect onboarding.

    See the Standard onboarding or Express onboarding documentation for information about prefilling information and account onboarding steps. Learn more about handling identity verification with the API.

    + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|bool $payouts_enabled Whether EDD\Vendor\Stripe can send payouts to this account. + * @property null|\EDD\Vendor\Stripe\StripeObject $requirements + * @property null|\EDD\Vendor\Stripe\StripeObject $settings Options for customizing how the account functions within Stripe. + * @property null|\EDD\Vendor\Stripe\StripeObject $tos_acceptance + * @property null|string $type The EDD\Vendor\Stripe account type. Can be standard, express, custom, or none. + */ +class Account extends ApiResource +{ + const OBJECT_NAME = 'account'; + + use ApiOperations\NestedResource; + use ApiOperations\Update; + + const BUSINESS_TYPE_COMPANY = 'company'; + const BUSINESS_TYPE_GOVERNMENT_ENTITY = 'government_entity'; + const BUSINESS_TYPE_INDIVIDUAL = 'individual'; + const BUSINESS_TYPE_NON_PROFIT = 'non_profit'; + + const TYPE_CUSTOM = 'custom'; + const TYPE_EXPRESS = 'express'; + const TYPE_NONE = 'none'; + const TYPE_STANDARD = 'standard'; + + /** + * With Connect, you can create EDD\Vendor\Stripe accounts for + * your users. To do this, you’ll first need to register your + * platform. + * + * If you’ve already collected information for your connected accounts, you can prefill that information + * when creating the account. Connect Onboarding won’t ask for the prefilled + * information during account onboarding. You can prefill any information on the + * account. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * With Connect, you can delete accounts you manage. + * + * Test-mode accounts can be deleted at any time. + * + * Live-mode accounts where EDD\Vendor\Stripe is responsible for negative account balances + * cannot be deleted, which includes Standard accounts. Live-mode accounts where + * your platform is liable for negative account balances, which includes Custom and + * Express accounts, can be deleted when all balances are zero. + * + * If you want to delete your own account, use the account information tab in + * your account settings instead. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of accounts connected to your platform via Connect. If you’re not a platform, the list is empty. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Account> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Updates a connected account by setting the + * values of the parameters passed. Any parameters not provided are left unchanged. + * + * For accounts where controller.requirement_collection + * is application, which includes Custom accounts, you can update any + * information on the account. + * + * For accounts where controller.requirement_collection + * is stripe, which includes Standard and Express accounts, you can + * update all information until you create an Account + * Link or Account Session to start Connect + * onboarding, after which some properties can no longer be updated. + * + * To update your own account, use the Dashboard. Refer to our + * Connect documentation to learn + * more about updating accounts. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + use ApiOperations\Retrieve { + retrieve as protected _retrieve; + } + + public static function getSavedNestedResources() + { + static $savedNestedResources = null; + if (null === $savedNestedResources) { + $savedNestedResources = new Util\Set([ + 'external_account', + 'bank_account', + ]); + } + + return $savedNestedResources; + } + + public function instanceUrl() + { + if (null === $this['id']) { + return '/v1/account'; + } + + return parent::instanceUrl(); + } + + /** + * @param null|array|string $id the ID of the account to retrieve, or an + * options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account + */ + public static function retrieve($id = null, $opts = null) + { + if (!$opts && \is_string($id) && 'sk_' === \substr($id, 0, 3)) { + $opts = $id; + $id = null; + } + + return self::_retrieve($id, $opts); + } + + public function serializeParameters($force = false) + { + $update = parent::serializeParameters($force); + if (isset($this->_values['legal_entity'])) { + $entity = $this['legal_entity']; + if (isset($entity->_values['additional_owners'])) { + $owners = $entity['additional_owners']; + $entityUpdate = isset($update['legal_entity']) ? $update['legal_entity'] : []; + $entityUpdate['additional_owners'] = $this->serializeAdditionalOwners($entity, $owners); + $update['legal_entity'] = $entityUpdate; + } + } + if (isset($this->_values['individual'])) { + $individual = $this['individual']; + if (($individual instanceof Person) && !isset($update['individual'])) { + $update['individual'] = $individual->serializeParameters($force); + } + } + + return $update; + } + + private function serializeAdditionalOwners($legalEntity, $additionalOwners) + { + if (isset($legalEntity->_originalValues['additional_owners'])) { + $originalValue = $legalEntity->_originalValues['additional_owners']; + } else { + $originalValue = []; + } + if (($originalValue) && (\count($originalValue) > \count($additionalOwners))) { + throw new Exception\InvalidArgumentException( + 'You cannot delete an item from an array, you must instead set a new array' + ); + } + + $updateArr = []; + foreach ($additionalOwners as $i => $v) { + $update = ($v instanceof StripeObject) ? $v->serializeParameters() : $v; + + if ([] !== $update) { + if (!$originalValue + || !\array_key_exists($i, $originalValue) + || ($update !== $legalEntity->serializeParamsValue($originalValue[$i], null, false, true))) { + $updateArr[$i] = $update; + } + } + } + + return $updateArr; + } + + /** + * @param null|array $clientId + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\StripeObject object containing the response from the API + */ + public function deauthorize($clientId = null, $opts = null) + { + $params = [ + 'client_id' => $clientId, + 'stripe_user_id' => $this->id, + ]; + + return OAuth::deauthorize($params, $opts); + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account the rejected account + */ + public function reject($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/reject'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + const PATH_CAPABILITIES = '/capabilities'; + + /** + * @param string $id the ID of the account on which to retrieve the capabilities + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Capability> the list of capabilities + */ + public static function allCapabilities($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_CAPABILITIES, $params, $opts); + } + + /** + * @param string $id the ID of the account to which the capability belongs + * @param string $capabilityId the ID of the capability to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Capability + */ + public static function retrieveCapability($id, $capabilityId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_CAPABILITIES, $capabilityId, $params, $opts); + } + + /** + * @param string $id the ID of the account to which the capability belongs + * @param string $capabilityId the ID of the capability to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Capability + */ + public static function updateCapability($id, $capabilityId, $params = null, $opts = null) + { + return self::_updateNestedResource($id, static::PATH_CAPABILITIES, $capabilityId, $params, $opts); + } + const PATH_EXTERNAL_ACCOUNTS = '/external_accounts'; + + /** + * @param string $id the ID of the account on which to retrieve the external accounts + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card> the list of external accounts (BankAccount or Card) + */ + public static function allExternalAccounts($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_EXTERNAL_ACCOUNTS, $params, $opts); + } + + /** + * @param string $id the ID of the account on which to create the external account + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card + */ + public static function createExternalAccount($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $params, $opts); + } + + /** + * @param string $id the ID of the account to which the external account belongs + * @param string $externalAccountId the ID of the external account to delete + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card + */ + public static function deleteExternalAccount($id, $externalAccountId, $params = null, $opts = null) + { + return self::_deleteNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts); + } + + /** + * @param string $id the ID of the account to which the external account belongs + * @param string $externalAccountId the ID of the external account to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card + */ + public static function retrieveExternalAccount($id, $externalAccountId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts); + } + + /** + * @param string $id the ID of the account to which the external account belongs + * @param string $externalAccountId the ID of the external account to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card + */ + public static function updateExternalAccount($id, $externalAccountId, $params = null, $opts = null) + { + return self::_updateNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts); + } + const PATH_LOGIN_LINKS = '/login_links'; + + /** + * @param string $id the ID of the account on which to create the login link + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\LoginLink + */ + public static function createLoginLink($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_LOGIN_LINKS, $params, $opts); + } + const PATH_PERSONS = '/persons'; + + /** + * @param string $id the ID of the account on which to retrieve the persons + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Person> the list of persons + */ + public static function allPersons($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_PERSONS, $params, $opts); + } + + /** + * @param string $id the ID of the account on which to create the person + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Person + */ + public static function createPerson($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_PERSONS, $params, $opts); + } + + /** + * @param string $id the ID of the account to which the person belongs + * @param string $personId the ID of the person to delete + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Person + */ + public static function deletePerson($id, $personId, $params = null, $opts = null) + { + return self::_deleteNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts); + } + + /** + * @param string $id the ID of the account to which the person belongs + * @param string $personId the ID of the person to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Person + */ + public static function retrievePerson($id, $personId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts); + } + + /** + * @param string $id the ID of the account to which the person belongs + * @param string $personId the ID of the person to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Person + */ + public static function updatePerson($id, $personId, $params = null, $opts = null) + { + return self::_updateNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/AccountLink.php b/libraries/Stripe/lib/AccountLink.php new file mode 100644 index 00000000000..4a46ef1ff42 --- /dev/null +++ b/libraries/Stripe/lib/AccountLink.php @@ -0,0 +1,45 @@ +Connect Onboarding + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property int $expires_at The timestamp at which this account link will expire. + * @property string $url The URL for the account link. + */ +class AccountLink extends ApiResource +{ + const OBJECT_NAME = 'account_link'; + + /** + * Creates an AccountLink object that includes a single-use EDD\Vendor\Stripe URL that the + * platform can redirect their user to in order to take them through the Connect + * Onboarding flow. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\AccountLink the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/AccountSession.php b/libraries/Stripe/lib/AccountSession.php new file mode 100644 index 00000000000..82be41dc566 --- /dev/null +++ b/libraries/Stripe/lib/AccountSession.php @@ -0,0 +1,49 @@ +Connect embedded components + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string $account The ID of the account the AccountSession was created for + * @property string $client_secret

    The client secret of this AccountSession. Used on the client to set up secure access to the given account.

    The client secret can be used to provide access to account from your frontend. It should not be stored, logged, or exposed to anyone other than the connected account. Make sure that you have TLS enabled on any page that includes the client secret.

    Refer to our docs to setup Connect embedded components and learn about how client_secret should be handled.

    + * @property \EDD\Vendor\Stripe\StripeObject $components + * @property int $expires_at The timestamp at which this AccountSession will expire. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + */ +class AccountSession extends ApiResource +{ + const OBJECT_NAME = 'account_session'; + + /** + * Creates a AccountSession object that includes a single-use token that the + * platform can use on their front-end to grant client-side API access. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\AccountSession the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/ApiOperations/All.php b/libraries/Stripe/lib/ApiOperations/All.php new file mode 100644 index 00000000000..61e8cd15745 --- /dev/null +++ b/libraries/Stripe/lib/ApiOperations/All.php @@ -0,0 +1,26 @@ +json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/ApiOperations/Delete.php b/libraries/Stripe/lib/ApiOperations/Delete.php new file mode 100644 index 00000000000..b37e840cd72 --- /dev/null +++ b/libraries/Stripe/lib/ApiOperations/Delete.php @@ -0,0 +1,30 @@ +instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/ApiOperations/NestedResource.php b/libraries/Stripe/lib/ApiOperations/NestedResource.php new file mode 100644 index 00000000000..6363096341a --- /dev/null +++ b/libraries/Stripe/lib/ApiOperations/NestedResource.php @@ -0,0 +1,135 @@ +json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param string $id + * @param string $nestedPath + * @param null|string $nestedId + * + * @return string + */ + protected static function _nestedResourceUrl($id, $nestedPath, $nestedId = null) + { + $url = static::resourceUrl($id) . $nestedPath; + if (null !== $nestedId) { + $url .= "/{$nestedId}"; + } + + return $url; + } + + /** + * @param string $id + * @param string $nestedPath + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\StripeObject + */ + protected static function _createNestedResource($id, $nestedPath, $params = null, $options = null) + { + $url = static::_nestedResourceUrl($id, $nestedPath); + + return self::_nestedResourceOperation('post', $url, $params, $options); + } + + /** + * @param string $id + * @param string $nestedPath + * @param null|string $nestedId + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\StripeObject + */ + protected static function _retrieveNestedResource($id, $nestedPath, $nestedId, $params = null, $options = null) + { + $url = static::_nestedResourceUrl($id, $nestedPath, $nestedId); + + return self::_nestedResourceOperation('get', $url, $params, $options); + } + + /** + * @param string $id + * @param string $nestedPath + * @param null|string $nestedId + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\StripeObject + */ + protected static function _updateNestedResource($id, $nestedPath, $nestedId, $params = null, $options = null) + { + $url = static::_nestedResourceUrl($id, $nestedPath, $nestedId); + + return self::_nestedResourceOperation('post', $url, $params, $options); + } + + /** + * @param string $id + * @param string $nestedPath + * @param null|string $nestedId + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\StripeObject + */ + protected static function _deleteNestedResource($id, $nestedPath, $nestedId, $params = null, $options = null) + { + $url = static::_nestedResourceUrl($id, $nestedPath, $nestedId); + + return self::_nestedResourceOperation('delete', $url, $params, $options); + } + + /** + * @param string $id + * @param string $nestedPath + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\StripeObject + */ + protected static function _allNestedResources($id, $nestedPath, $params = null, $options = null) + { + $url = static::_nestedResourceUrl($id, $nestedPath); + + return self::_nestedResourceOperation('get', $url, $params, $options); + } +} diff --git a/libraries/Stripe/lib/ApiOperations/Request.php b/libraries/Stripe/lib/ApiOperations/Request.php new file mode 100644 index 00000000000..74fd737fe63 --- /dev/null +++ b/libraries/Stripe/lib/ApiOperations/Request.php @@ -0,0 +1,132 @@ + 100, " + . "'currency' => 'usd', 'source' => 'tok_1234'])\")"; + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException($message); + } + } + + /** + * @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.) + * @param string $url URL for the request + * @param array $params list of parameters for the request + * @param null|array|string $options + * @param string[] $usage names of tracked behaviors associated with this request + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return array tuple containing (the JSON response, $options) + */ + protected function _request($method, $url, $params = [], $options = null, $usage = []) + { + $opts = $this->_opts->merge($options); + list($resp, $options) = static::_staticRequest($method, $url, $params, $opts, $usage); + $this->setLastResponse($resp); + + return [$resp->json, $options]; + } + + /** + * @param string $url URL for the request + * @param class-string< \EDD\Vendor\Stripe\SearchResult|\EDD\Vendor\Stripe\Collection > $resultClass indicating what type of paginated result is returned + * @param null|array $params list of parameters for the request + * @param null|array|string $options + * @param string[] $usage names of tracked behaviors associated with this request + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection|\EDD\Vendor\Stripe\SearchResult + */ + protected static function _requestPage($url, $resultClass, $params = null, $options = null, $usage = []) + { + self::_validateParams($params); + + list($response, $opts) = static::_staticRequest('get', $url, $params, $options, $usage); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + if (!($obj instanceof $resultClass)) { + throw new \EDD\Vendor\Stripe\Exception\UnexpectedValueException( + 'Expected type ' . $resultClass . ', got "' . \get_class($obj) . '" instead.' + ); + } + $obj->setLastResponse($response); + $obj->setFilters($params); + + return $obj; + } + + /** + * @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.) + * @param string $url URL for the request + * @param callable $readBodyChunk function that will receive chunks of data from a successful request body + * @param array $params list of parameters for the request + * @param null|array|string $options + * @param string[] $usage names of tracked behaviors associated with this request + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + */ + protected function _requestStream($method, $url, $readBodyChunk, $params = [], $options = null, $usage = []) + { + $opts = $this->_opts->merge($options); + static::_staticStreamingRequest($method, $url, $readBodyChunk, $params, $opts, $usage); + } + + /** + * @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.) + * @param string $url URL for the request + * @param array $params list of parameters for the request + * @param null|array|string $options + * @param string[] $usage names of tracked behaviors associated with this request + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return array tuple containing (the JSON response, $options) + */ + protected static function _staticRequest($method, $url, $params, $options, $usage = []) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($options); + $baseUrl = isset($opts->apiBase) ? $opts->apiBase : static::baseUrl(); + $requestor = new \EDD\Vendor\Stripe\ApiRequestor($opts->apiKey, $baseUrl); + list($response, $opts->apiKey) = $requestor->request($method, $url, $params, $opts->headers, $usage); + $opts->discardNonPersistentHeaders(); + + return [$response, $opts]; + } + + /** + * @param 'delete'|'get'|'post' $method HTTP method ('get', 'post', etc.) + * @param string $url URL for the request + * @param callable $readBodyChunk function that will receive chunks of data from a successful request body + * @param array $params list of parameters for the request + * @param null|array|string $options + * @param string[] $usage names of tracked behaviors associated with this request + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + */ + protected static function _staticStreamingRequest($method, $url, $readBodyChunk, $params, $options, $usage = []) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($options); + $baseUrl = isset($opts->apiBase) ? $opts->apiBase : static::baseUrl(); + $requestor = new \EDD\Vendor\Stripe\ApiRequestor($opts->apiKey, $baseUrl); + $requestor->requestStream($method, $url, $readBodyChunk, $params, $opts->headers); + } +} diff --git a/libraries/Stripe/lib/ApiOperations/Retrieve.php b/libraries/Stripe/lib/ApiOperations/Retrieve.php new file mode 100644 index 00000000000..2482063b8d8 --- /dev/null +++ b/libraries/Stripe/lib/ApiOperations/Retrieve.php @@ -0,0 +1,30 @@ +refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/ApiOperations/Search.php b/libraries/Stripe/lib/ApiOperations/Search.php new file mode 100644 index 00000000000..be370b00066 --- /dev/null +++ b/libraries/Stripe/lib/ApiOperations/Search.php @@ -0,0 +1,25 @@ +refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/ApiOperations/Update.php b/libraries/Stripe/lib/ApiOperations/Update.php new file mode 100644 index 00000000000..2056818c38e --- /dev/null +++ b/libraries/Stripe/lib/ApiOperations/Update.php @@ -0,0 +1,56 @@ +json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return static the saved resource + * + * @deprecated The `save` method is deprecated and will be removed in a + * future major version of the library. Use the static method `update` + * on the resource instead. + */ + public function save($opts = null) + { + $params = $this->serializeParameters(); + if (\count($params) > 0) { + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']); + $this->refreshFrom($response, $opts); + } + + return $this; + } +} diff --git a/libraries/Stripe/lib/ApiRequestor.php b/libraries/Stripe/lib/ApiRequestor.php new file mode 100644 index 00000000000..83b3a04908d --- /dev/null +++ b/libraries/Stripe/lib/ApiRequestor.php @@ -0,0 +1,636 @@ +_apiKey = $apiKey; + if (!$apiBase) { + $apiBase = Stripe::$apiBase; + } + $this->_apiBase = $apiBase; + $this->_appInfo = $appInfo; + } + + /** + * Creates a telemetry json blob for use in 'X-Stripe-Client-Telemetry' headers. + * + * @static + * + * @param RequestTelemetry $requestTelemetry + * + * @return string + */ + private static function _telemetryJson($requestTelemetry) + { + $payload = [ + 'last_request_metrics' => [ + 'request_id' => $requestTelemetry->requestId, + 'request_duration_ms' => $requestTelemetry->requestDuration, + ], + ]; + if (\count($requestTelemetry->usage) > 0) { + $payload['last_request_metrics']['usage'] = $requestTelemetry->usage; + } + + $result = \json_encode($payload); + if (false !== $result) { + return $result; + } + Stripe::getLogger()->error('Serializing telemetry payload failed!'); + + return '{}'; + } + + /** + * @static + * + * @param ApiResource|array|bool|mixed $d + * + * @return ApiResource|array|mixed|string + */ + private static function _encodeObjects($d) + { + if ($d instanceof ApiResource) { + return Util\Util::utf8($d->id); + } + if (true === $d) { + return 'true'; + } + if (false === $d) { + return 'false'; + } + if (\is_array($d)) { + $res = []; + foreach ($d as $k => $v) { + $res[$k] = self::_encodeObjects($v); + } + + return $res; + } + + return Util\Util::utf8($d); + } + + /** + * @param 'delete'|'get'|'post' $method + * @param string $url + * @param null|array $params + * @param null|array $headers + * @param string[] $usage + * + * @throws Exception\ApiErrorException + * + * @return array tuple containing (ApiReponse, API key) + */ + public function request($method, $url, $params = null, $headers = null, $usage = []) + { + $params = $params ?: []; + $headers = $headers ?: []; + list($rbody, $rcode, $rheaders, $myApiKey) = + $this->_requestRaw($method, $url, $params, $headers, $usage); + $json = $this->_interpretResponse($rbody, $rcode, $rheaders); + $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); + + return [$resp, $myApiKey]; + } + + /** + * @param 'delete'|'get'|'post' $method + * @param string $url + * @param callable $readBodyChunkCallable + * @param null|array $params + * @param null|array $headers + * @param string[] $usage + * + * @throws Exception\ApiErrorException + */ + public function requestStream($method, $url, $readBodyChunkCallable, $params = null, $headers = null, $usage = []) + { + $params = $params ?: []; + $headers = $headers ?: []; + list($rbody, $rcode, $rheaders, $myApiKey) = + $this->_requestRawStreaming($method, $url, $params, $headers, $usage, $readBodyChunkCallable); + if ($rcode >= 300) { + $this->_interpretResponse($rbody, $rcode, $rheaders); + } + } + + /** + * @param string $rbody a JSON string + * @param int $rcode + * @param array $rheaders + * @param array $resp + * + * @throws Exception\UnexpectedValueException + * @throws Exception\ApiErrorException + */ + public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) + { + if (!\is_array($resp) || !isset($resp['error'])) { + $msg = "Invalid response object from API: {$rbody} " + . "(HTTP response code was {$rcode})"; + + throw new Exception\UnexpectedValueException($msg); + } + + $errorData = $resp['error']; + + $error = null; + if (\is_string($errorData)) { + $error = self::_specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorData); + } + if (!$error) { + $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); + } + + throw $error; + } + + /** + * @static + * + * @param string $rbody + * @param int $rcode + * @param array $rheaders + * @param array $resp + * @param array $errorData + * + * @return Exception\ApiErrorException + */ + private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) + { + $msg = isset($errorData['message']) ? $errorData['message'] : null; + $param = isset($errorData['param']) ? $errorData['param'] : null; + $code = isset($errorData['code']) ? $errorData['code'] : null; + $type = isset($errorData['type']) ? $errorData['type'] : null; + $declineCode = isset($errorData['decline_code']) ? $errorData['decline_code'] : null; + + switch ($rcode) { + case 400: + // 'rate_limit' code is deprecated, but left here for backwards compatibility + // for API versions earlier than 2015-09-08 + if ('rate_limit' === $code) { + return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param); + } + if ('idempotency_error' === $type) { + return Exception\IdempotencyException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code); + } + + // no break + case 404: + return Exception\InvalidRequestException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param); + + case 401: + return Exception\AuthenticationException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code); + + case 402: + return Exception\CardException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $declineCode, $param); + + case 403: + return Exception\PermissionException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code); + + case 429: + return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param); + + default: + return Exception\UnknownApiErrorException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code); + } + } + + /** + * @static + * + * @param bool|string $rbody + * @param int $rcode + * @param array $rheaders + * @param array $resp + * @param string $errorCode + * + * @return Exception\OAuth\OAuthErrorException + */ + private static function _specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorCode) + { + $description = isset($resp['error_description']) ? $resp['error_description'] : $errorCode; + + switch ($errorCode) { + case 'invalid_client': + return Exception\OAuth\InvalidClientException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode); + + case 'invalid_grant': + return Exception\OAuth\InvalidGrantException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode); + + case 'invalid_request': + return Exception\OAuth\InvalidRequestException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode); + + case 'invalid_scope': + return Exception\OAuth\InvalidScopeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode); + + case 'unsupported_grant_type': + return Exception\OAuth\UnsupportedGrantTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode); + + case 'unsupported_response_type': + return Exception\OAuth\UnsupportedResponseTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode); + + default: + return Exception\OAuth\UnknownOAuthErrorException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode); + } + } + + /** + * @static + * + * @param null|array $appInfo + * + * @return null|string + */ + private static function _formatAppInfo($appInfo) + { + if (null !== $appInfo) { + $string = $appInfo['name']; + if (\array_key_exists('version', $appInfo) && null !== $appInfo['version']) { + $string .= '/' . $appInfo['version']; + } + if (\array_key_exists('url', $appInfo) && null !== $appInfo['url']) { + $string .= ' (' . $appInfo['url'] . ')'; + } + + return $string; + } + + return null; + } + + /** + * @static + * + * @param string $disableFunctionsOutput - String value of the 'disable_function' setting, as output by \ini_get('disable_functions') + * @param string $functionName - Name of the function we are interesting in seeing whether or not it is disabled + * + * @return bool + */ + private static function _isDisabled($disableFunctionsOutput, $functionName) + { + $disabledFunctions = \explode(',', $disableFunctionsOutput); + foreach ($disabledFunctions as $disabledFunction) { + if (\trim($disabledFunction) === $functionName) { + return true; + } + } + + return false; + } + + /** + * @static + * + * @param string $apiKey the EDD\Vendor\Stripe API key, to be used in regular API requests + * @param null $clientInfo client user agent information + * @param null $appInfo information to identify a plugin that integrates EDD\Vendor\Stripe using this library + * + * @return array + */ + private static function _defaultHeaders($apiKey, $clientInfo = null, $appInfo = null) + { + $uaString = 'Stripe/v1 PhpBindings/' . Stripe::VERSION; + + $langVersion = \PHP_VERSION; + $uname_disabled = self::_isDisabled(\ini_get('disable_functions'), 'php_uname'); + $uname = $uname_disabled ? '(disabled)' : \php_uname(); + + // Fallback to global configuration to maintain backwards compatibility. + $appInfo = $appInfo ?: Stripe::getAppInfo(); + $ua = [ + 'bindings_version' => Stripe::VERSION, + 'lang' => 'php', + 'lang_version' => $langVersion, + 'publisher' => 'stripe', + 'uname' => $uname, + ]; + if ($clientInfo) { + $ua = \array_merge($clientInfo, $ua); + } + if (null !== $appInfo) { + $uaString .= ' ' . self::_formatAppInfo($appInfo); + $ua['application'] = $appInfo; + } + + return [ + 'X-Stripe-Client-User-Agent' => \json_encode($ua), + 'User-Agent' => $uaString, + 'Authorization' => 'Bearer ' . $apiKey, + 'Stripe-Version' => Stripe::getApiVersion(), + ]; + } + + private function _prepareRequest($method, $url, $params, $headers) + { + $myApiKey = $this->_apiKey; + if (!$myApiKey) { + $myApiKey = Stripe::$apiKey; + } + + if (!$myApiKey) { + $msg = 'No API key provided. (HINT: set your API key using ' + . '"Stripe::setApiKey()". You can generate API keys from ' + . 'the EDD\Vendor\Stripe web interface. See https://stripe.com/api for ' + . 'details, or email support@stripe.com if you have any questions.'; + + throw new Exception\AuthenticationException($msg); + } + + // Clients can supply arbitrary additional keys to be included in the + // X-Stripe-Client-User-Agent header via the optional getUserAgentInfo() + // method + $clientUAInfo = null; + if (\method_exists($this->httpClient(), 'getUserAgentInfo')) { + $clientUAInfo = $this->httpClient()->getUserAgentInfo(); + } + + if ($params && \is_array($params)) { + $optionKeysInParams = \array_filter( + self::$OPTIONS_KEYS, + function ($key) use ($params) { + return \array_key_exists($key, $params); + } + ); + if (\count($optionKeysInParams) > 0) { + $message = \sprintf('Options found in $params: %s. Options should ' + . 'be passed in their own array after $params. (HINT: pass an ' + . 'empty array to $params if you do not have any.)', \implode(', ', $optionKeysInParams)); + \trigger_error($message, \E_USER_WARNING); + } + } + + $absUrl = $this->_apiBase . $url; + $params = self::_encodeObjects($params); + $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo, $this->_appInfo); + + if (Stripe::$accountId) { + $defaultHeaders['Stripe-Account'] = Stripe::$accountId; + } + + if (Stripe::$enableTelemetry && null !== self::$requestTelemetry) { + $defaultHeaders['X-Stripe-Client-Telemetry'] = self::_telemetryJson(self::$requestTelemetry); + } + + $hasFile = false; + foreach ($params as $k => $v) { + if (\is_resource($v)) { + $hasFile = true; + $params[$k] = self::_processResourceParam($v); + } elseif ($v instanceof \CURLFile) { + $hasFile = true; + } + } + + if ($hasFile) { + $defaultHeaders['Content-Type'] = 'multipart/form-data'; + } else { + $defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + $combinedHeaders = \array_merge($defaultHeaders, $headers); + $rawHeaders = []; + + foreach ($combinedHeaders as $header => $value) { + $rawHeaders[] = $header . ': ' . $value; + } + + return [$absUrl, $rawHeaders, $params, $hasFile, $myApiKey]; + } + + /** + * @param 'delete'|'get'|'post' $method + * @param string $url + * @param array $params + * @param array $headers + * @param string[] $usage + * + * @throws Exception\AuthenticationException + * @throws Exception\ApiConnectionException + * + * @return array + */ + private function _requestRaw($method, $url, $params, $headers, $usage) + { + list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers); + + $requestStartMs = Util\Util::currentTimeMillis(); + + list($rbody, $rcode, $rheaders) = $this->httpClient()->request( + $method, + $absUrl, + $rawHeaders, + $params, + $hasFile + ); + + if ( + isset($rheaders['request-id']) + && \is_string($rheaders['request-id']) + && '' !== $rheaders['request-id'] + ) { + self::$requestTelemetry = new RequestTelemetry( + $rheaders['request-id'], + Util\Util::currentTimeMillis() - $requestStartMs, + $usage + ); + } + + return [$rbody, $rcode, $rheaders, $myApiKey]; + } + + /** + * @param 'delete'|'get'|'post' $method + * @param string $url + * @param array $params + * @param array $headers + * @param string[] $usage + * @param callable $readBodyChunkCallable + * + * @throws Exception\AuthenticationException + * @throws Exception\ApiConnectionException + * + * @return array + */ + private function _requestRawStreaming($method, $url, $params, $headers, $usage, $readBodyChunkCallable) + { + list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers); + + $requestStartMs = Util\Util::currentTimeMillis(); + + list($rbody, $rcode, $rheaders) = $this->streamingHttpClient()->requestStream( + $method, + $absUrl, + $rawHeaders, + $params, + $hasFile, + $readBodyChunkCallable + ); + + if ( + isset($rheaders['request-id']) + && \is_string($rheaders['request-id']) + && '' !== $rheaders['request-id'] + ) { + self::$requestTelemetry = new RequestTelemetry( + $rheaders['request-id'], + Util\Util::currentTimeMillis() - $requestStartMs + ); + } + + return [$rbody, $rcode, $rheaders, $myApiKey]; + } + + /** + * @param resource $resource + * + * @throws Exception\InvalidArgumentException + * + * @return \CURLFile|string + */ + private function _processResourceParam($resource) + { + if ('stream' !== \get_resource_type($resource)) { + throw new Exception\InvalidArgumentException( + 'Attempted to upload a resource that is not a stream' + ); + } + + $metaData = \stream_get_meta_data($resource); + if ('plainfile' !== $metaData['wrapper_type']) { + throw new Exception\InvalidArgumentException( + 'Only plainfile resource streams are supported' + ); + } + + // We don't have the filename or mimetype, but the API doesn't care + return new \CURLFile($metaData['uri']); + } + + /** + * @param string $rbody + * @param int $rcode + * @param array $rheaders + * + * @throws Exception\UnexpectedValueException + * @throws Exception\ApiErrorException + * + * @return array + */ + private function _interpretResponse($rbody, $rcode, $rheaders) + { + $resp = \json_decode($rbody, true); + $jsonError = \json_last_error(); + if (null === $resp && \JSON_ERROR_NONE !== $jsonError) { + $msg = "Invalid response body from API: {$rbody} " + . "(HTTP response code was {$rcode}, json_last_error() was {$jsonError})"; + + throw new Exception\UnexpectedValueException($msg, $rcode); + } + + if ($rcode < 200 || $rcode >= 300) { + $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); + } + + return $resp; + } + + /** + * @static + * + * @param HttpClient\ClientInterface $client + */ + public static function setHttpClient($client) + { + self::$_httpClient = $client; + } + + /** + * @static + * + * @param HttpClient\StreamingClientInterface $client + */ + public static function setStreamingHttpClient($client) + { + self::$_streamingHttpClient = $client; + } + + /** + * @static + * + * Resets any stateful telemetry data + */ + public static function resetTelemetry() + { + self::$requestTelemetry = null; + } + + /** + * @return HttpClient\ClientInterface + */ + private function httpClient() + { + if (!self::$_httpClient) { + self::$_httpClient = HttpClient\CurlClient::instance(); + } + + return self::$_httpClient; + } + + /** + * @return HttpClient\StreamingClientInterface + */ + private function streamingHttpClient() + { + if (!self::$_streamingHttpClient) { + self::$_streamingHttpClient = HttpClient\CurlClient::instance(); + } + + return self::$_streamingHttpClient; + } +} diff --git a/libraries/Stripe/lib/ApiResource.php b/libraries/Stripe/lib/ApiResource.php new file mode 100644 index 00000000000..6c3544a296a --- /dev/null +++ b/libraries/Stripe/lib/ApiResource.php @@ -0,0 +1,123 @@ +{$k}; + if ((static::getSavedNestedResources()->includes($k)) + && ($v instanceof ApiResource)) { + $v->saveWithParent = true; + } + } + + /** + * @throws Exception\ApiErrorException + * + * @return ApiResource the refreshed resource + */ + public function refresh() + { + $requestor = new ApiRequestor($this->_opts->apiKey, static::baseUrl()); + $url = $this->instanceUrl(); + + list($response, $this->_opts->apiKey) = $requestor->request( + 'get', + $url, + $this->_retrieveOptions, + $this->_opts->headers + ); + $this->setLastResponse($response); + $this->refreshFrom($response->json, $this->_opts); + + return $this; + } + + /** + * @return string the base URL for the given class + */ + public static function baseUrl() + { + return Stripe::$apiBase; + } + + /** + * @return string the endpoint URL for the given class + */ + public static function classUrl() + { + // Replace dots with slashes for namespaced resources, e.g. if the object's name is + // "foo.bar", then its URL will be "/v1/foo/bars". + + /** @phpstan-ignore-next-line */ + $base = \str_replace('.', '/', static::OBJECT_NAME); + + return "/v1/{$base}s"; + } + + /** + * @param null|string $id the ID of the resource + * + * @throws Exception\UnexpectedValueException if $id is null + * + * @return string the instance endpoint URL for the given class + */ + public static function resourceUrl($id) + { + if (null === $id) { + $class = static::class; + $message = 'Could not determine which URL to request: ' + . "{$class} instance has invalid ID: {$id}"; + + throw new Exception\UnexpectedValueException($message); + } + $id = Util\Util::utf8($id); + $base = static::classUrl(); + $extn = \urlencode($id); + + return "{$base}/{$extn}"; + } + + /** + * @return string the full API URL for this API resource + */ + public function instanceUrl() + { + return static::resourceUrl($this['id']); + } +} diff --git a/libraries/Stripe/lib/ApiResponse.php b/libraries/Stripe/lib/ApiResponse.php new file mode 100644 index 00000000000..fa4defacadb --- /dev/null +++ b/libraries/Stripe/lib/ApiResponse.php @@ -0,0 +1,45 @@ +body = $body; + $this->code = $code; + $this->headers = $headers; + $this->json = $json; + } +} diff --git a/libraries/Stripe/lib/ApplePayDomain.php b/libraries/Stripe/lib/ApplePayDomain.php new file mode 100644 index 00000000000..4c81e9eb8d2 --- /dev/null +++ b/libraries/Stripe/lib/ApplePayDomain.php @@ -0,0 +1,105 @@ +true
    if the object exists in live mode or the value false if the object exists in test mode. + */ +class ApplePayDomain extends ApiResource +{ + const OBJECT_NAME = 'apple_pay_domain'; + + /** + * Create an apple pay domain. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplePayDomain the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Delete an apple pay domain. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplePayDomain the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * List apple pay domains. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ApplePayDomain> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieve an apple pay domain. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplePayDomain + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @return string The class URL for this resource. It needs to be special + * cased because it doesn't fit into the standard resource pattern. + */ + public static function classUrl() + { + return '/v1/apple_pay/domains'; + } +} diff --git a/libraries/Stripe/lib/Application.php b/libraries/Stripe/lib/Application.php new file mode 100644 index 00000000000..d08597a42d8 --- /dev/null +++ b/libraries/Stripe/lib/Application.php @@ -0,0 +1,15 @@ +ISO currency code, in lowercase. Must be a supported currency. + * @property null|\EDD\Vendor\Stripe\StripeObject $fee_source Polymorphic source of the application fee. Includes the ID of the object the application fee was created from. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string|\EDD\Vendor\Stripe\Charge $originating_transaction ID of the corresponding charge on the platform account, if this fee was the result of a charge using the destination parameter. + * @property bool $refunded Whether the fee has been fully refunded. If the fee is only partially refunded, this attribute will still be false. + * @property \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ApplicationFeeRefund> $refunds A list of refunds that have been applied to the fee. + */ +class ApplicationFee extends ApiResource +{ + const OBJECT_NAME = 'application_fee'; + + use ApiOperations\NestedResource; + + /** + * Returns a list of application fees you’ve previously collected. The application + * fees are returned in sorted order, with the most recent fees appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ApplicationFee> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an application fee that your account has collected. The + * same information is returned when refunding the application fee. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplicationFee + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + const PATH_REFUNDS = '/refunds'; + + /** + * @param string $id the ID of the application fee on which to retrieve the application fee refunds + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ApplicationFeeRefund> the list of application fee refunds + */ + public static function allRefunds($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_REFUNDS, $params, $opts); + } + + /** + * @param string $id the ID of the application fee on which to create the application fee refund + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplicationFeeRefund + */ + public static function createRefund($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_REFUNDS, $params, $opts); + } + + /** + * @param string $id the ID of the application fee to which the application fee refund belongs + * @param string $refundId the ID of the application fee refund to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplicationFeeRefund + */ + public static function retrieveRefund($id, $refundId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_REFUNDS, $refundId, $params, $opts); + } + + /** + * @param string $id the ID of the application fee to which the application fee refund belongs + * @param string $refundId the ID of the application fee refund to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplicationFeeRefund + */ + public static function updateRefund($id, $refundId, $params = null, $opts = null) + { + return self::_updateNestedResource($id, static::PATH_REFUNDS, $refundId, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/ApplicationFeeRefund.php b/libraries/Stripe/lib/ApplicationFeeRefund.php new file mode 100644 index 00000000000..596b21895dd --- /dev/null +++ b/libraries/Stripe/lib/ApplicationFeeRefund.php @@ -0,0 +1,64 @@ +Application Fee Refund
    objects allow you to refund an application fee that + * has previously been created but not yet refunded. Funds will be refunded to + * the EDD\Vendor\Stripe account from which the fee was originally collected. + * + * Related guide: Refunding application fees + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount, in cents (or local equivalent). + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $balance_transaction Balance transaction that describes the impact on your account balance. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string|\EDD\Vendor\Stripe\ApplicationFee $fee ID of the application fee that was refunded. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + */ +class ApplicationFeeRefund extends ApiResource +{ + const OBJECT_NAME = 'fee_refund'; + + use ApiOperations\Update { + save as protected _save; + } + + /** + * @return string the API URL for this EDD\Vendor\Stripe refund + */ + public function instanceUrl() + { + $id = $this['id']; + $fee = $this['fee']; + if (!$id) { + throw new Exception\UnexpectedValueException( + 'Could not determine which URL to request: ' . + "class instance has invalid ID: {$id}", + null + ); + } + $id = Util\Util::utf8($id); + $fee = Util\Util::utf8($fee); + + $base = ApplicationFee::classUrl(); + $feeExtn = \urlencode($fee); + $extn = \urlencode($id); + + return "{$base}/{$feeExtn}/refunds/{$extn}"; + } + + /** + * @param null|array|string $opts + * + * @return ApplicationFeeRefund the saved refund + */ + public function save($opts = null) + { + return $this->_save($opts); + } +} diff --git a/libraries/Stripe/lib/Apps/Secret.php b/libraries/Stripe/lib/Apps/Secret.php new file mode 100644 index 00000000000..6e635b1af96 --- /dev/null +++ b/libraries/Stripe/lib/Apps/Secret.php @@ -0,0 +1,106 @@ +secret
    . Other apps can't view secrets created by an app. Additionally, secrets are scoped to provide further permission control. + * + * All Dashboard users and the app backend share account scoped secrets. Use the account scope for secrets that don't change per-user, like a third-party API key. + * + * A user scoped secret is accessible by the app backend and one specific Dashboard user. Use the user scope for per-user secrets like per-user OAuth tokens, where different users might have different permissions. + * + * Related guide: Store data between page reloads + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|bool $deleted If true, indicates that this secret has been deleted + * @property null|int $expires_at The Unix timestamp for the expiry time of the secret, after which the secret deletes. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $name A name for the secret that's unique within the scope. + * @property null|string $payload The plaintext secret value to be stored. + * @property \EDD\Vendor\Stripe\StripeObject $scope + */ +class Secret extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'apps.secret'; + + /** + * Create or replace a secret in the secret store. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Apps\Secret the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * List all secrets stored on the given scope. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Apps\Secret> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Apps\Secret the deleted secret + */ + public static function deleteWhere($params = null, $opts = null) + { + $url = static::classUrl() . '/delete'; + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Apps\Secret the finded secret + */ + public static function find($params = null, $opts = null) + { + $url = static::classUrl() . '/find'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Balance.php b/libraries/Stripe/lib/Balance.php new file mode 100644 index 00000000000..807863882bb --- /dev/null +++ b/libraries/Stripe/lib/Balance.php @@ -0,0 +1,52 @@ +transactions that contributed to the balance + * (charges, payouts, and so forth). + * + * The available and pending amounts for each currency are broken down further by + * payment source types. + * + * Related guide: Understanding Connect account balances + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property \EDD\Vendor\Stripe\StripeObject[] $available Available funds that you can transfer or pay out automatically by EDD\Vendor\Stripe or explicitly through the Transfers API or Payouts API. You can find the available balance for each currency and payment type in the source_types property. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $connect_reserved Funds held due to negative balances on connected accounts where account.controller.requirement_collection is application, which includes Custom accounts. You can find the connect reserve balance for each currency and payment type in the source_types property. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $instant_available Funds that you can pay out using Instant Payouts. + * @property null|\EDD\Vendor\Stripe\StripeObject $issuing + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject[] $pending Funds that aren't available in the balance yet. You can find the pending balance for each currency and each payment type in the source_types property. + */ +class Balance extends SingletonApiResource +{ + const OBJECT_NAME = 'balance'; + + /** + * Retrieves the current account balance, based on the authentication that was used + * to make the request. For a sample request, see Accounting + * for negative balances. + * + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Balance + */ + public static function retrieve($opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static(null, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/BalanceTransaction.php b/libraries/Stripe/lib/BalanceTransaction.php new file mode 100644 index 00000000000..22af16676cf --- /dev/null +++ b/libraries/Stripe/lib/BalanceTransaction.php @@ -0,0 +1,117 @@ +Balance transaction types + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Gross amount of this transaction (in cents (or local equivalent)). A positive value represents funds charged to another party, and a negative value represents funds sent to another party. + * @property int $available_on The date that the transaction's net funds become available in the EDD\Vendor\Stripe balance. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|float $exchange_rate If applicable, this transaction uses an exchange rate. If money converts from currency A to currency B, then the amount in currency A, multipled by the exchange_rate, equals the amount in currency B. For example, if you charge a customer 10.00 EUR, the PaymentIntent's amount is 1000 and currency is eur. If this converts to 12.34 USD in your EDD\Vendor\Stripe account, the BalanceTransaction's amount is 1234, its currency is usd, and the exchange_rate is 1.234. + * @property int $fee Fees (in cents (or local equivalent)) paid for this transaction. Represented as a positive integer when assessed. + * @property \EDD\Vendor\Stripe\StripeObject[] $fee_details Detailed breakdown of fees (in cents (or local equivalent)) paid for this transaction. + * @property int $net Net impact to a EDD\Vendor\Stripe balance (in cents (or local equivalent)). A positive value represents incrementing a EDD\Vendor\Stripe balance, and a negative value decrementing a EDD\Vendor\Stripe balance. You can calculate the net impact of a transaction on a balance by amount - fee + * @property string $reporting_category Learn more about how reporting categories can help you understand balance transactions from an accounting perspective. + * @property null|string|\EDD\Vendor\Stripe\ApplicationFee|\EDD\Vendor\Stripe\ApplicationFeeRefund|\EDD\Vendor\Stripe\Charge|\EDD\Vendor\Stripe\ConnectCollectionTransfer|\EDD\Vendor\Stripe\CustomerCashBalanceTransaction|\EDD\Vendor\Stripe\Dispute|\EDD\Vendor\Stripe\Issuing\Authorization|\EDD\Vendor\Stripe\Issuing\Dispute|\EDD\Vendor\Stripe\Issuing\Transaction|\EDD\Vendor\Stripe\Payout|\EDD\Vendor\Stripe\Refund|\EDD\Vendor\Stripe\ReserveTransaction|\EDD\Vendor\Stripe\TaxDeductedAtSource|\EDD\Vendor\Stripe\Topup|\EDD\Vendor\Stripe\Transfer|\EDD\Vendor\Stripe\TransferReversal $source This transaction relates to the EDD\Vendor\Stripe object. + * @property string $status The transaction's net funds status in the EDD\Vendor\Stripe balance, which are either available or pending. + * @property string $type Transaction type: adjustment, advance, advance_funding, anticipation_repayment, application_fee, application_fee_refund, charge, climate_order_purchase, climate_order_refund, connect_collection_transfer, contribution, issuing_authorization_hold, issuing_authorization_release, issuing_dispute, issuing_transaction, obligation_outbound, obligation_reversal_inbound, payment, payment_failure_refund, payment_network_reserve_hold, payment_network_reserve_release, payment_refund, payment_reversal, payment_unreconciled, payout, payout_cancel, payout_failure, refund, refund_failure, reserve_transaction, reserved_funds, stripe_fee, stripe_fx_fee, tax_fee, topup, topup_reversal, transfer, transfer_cancel, transfer_failure, or transfer_refund. Learn more about balance transaction types and what they represent. To classify transactions for accounting purposes, consider reporting_category instead. + */ +class BalanceTransaction extends ApiResource +{ + const OBJECT_NAME = 'balance_transaction'; + + const TYPE_ADJUSTMENT = 'adjustment'; + const TYPE_ADVANCE = 'advance'; + const TYPE_ADVANCE_FUNDING = 'advance_funding'; + const TYPE_ANTICIPATION_REPAYMENT = 'anticipation_repayment'; + const TYPE_APPLICATION_FEE = 'application_fee'; + const TYPE_APPLICATION_FEE_REFUND = 'application_fee_refund'; + const TYPE_CHARGE = 'charge'; + const TYPE_CLIMATE_ORDER_PURCHASE = 'climate_order_purchase'; + const TYPE_CLIMATE_ORDER_REFUND = 'climate_order_refund'; + const TYPE_CONNECT_COLLECTION_TRANSFER = 'connect_collection_transfer'; + const TYPE_CONTRIBUTION = 'contribution'; + const TYPE_ISSUING_AUTHORIZATION_HOLD = 'issuing_authorization_hold'; + const TYPE_ISSUING_AUTHORIZATION_RELEASE = 'issuing_authorization_release'; + const TYPE_ISSUING_DISPUTE = 'issuing_dispute'; + const TYPE_ISSUING_TRANSACTION = 'issuing_transaction'; + const TYPE_OBLIGATION_OUTBOUND = 'obligation_outbound'; + const TYPE_OBLIGATION_REVERSAL_INBOUND = 'obligation_reversal_inbound'; + const TYPE_PAYMENT = 'payment'; + const TYPE_PAYMENT_FAILURE_REFUND = 'payment_failure_refund'; + const TYPE_PAYMENT_NETWORK_RESERVE_HOLD = 'payment_network_reserve_hold'; + const TYPE_PAYMENT_NETWORK_RESERVE_RELEASE = 'payment_network_reserve_release'; + const TYPE_PAYMENT_REFUND = 'payment_refund'; + const TYPE_PAYMENT_REVERSAL = 'payment_reversal'; + const TYPE_PAYMENT_UNRECONCILED = 'payment_unreconciled'; + const TYPE_PAYOUT = 'payout'; + const TYPE_PAYOUT_CANCEL = 'payout_cancel'; + const TYPE_PAYOUT_FAILURE = 'payout_failure'; + const TYPE_REFUND = 'refund'; + const TYPE_REFUND_FAILURE = 'refund_failure'; + const TYPE_RESERVED_FUNDS = 'reserved_funds'; + const TYPE_RESERVE_TRANSACTION = 'reserve_transaction'; + const TYPE_STRIPE_FEE = 'stripe_fee'; + const TYPE_STRIPE_FX_FEE = 'stripe_fx_fee'; + const TYPE_TAX_FEE = 'tax_fee'; + const TYPE_TOPUP = 'topup'; + const TYPE_TOPUP_REVERSAL = 'topup_reversal'; + const TYPE_TRANSFER = 'transfer'; + const TYPE_TRANSFER_CANCEL = 'transfer_cancel'; + const TYPE_TRANSFER_FAILURE = 'transfer_failure'; + const TYPE_TRANSFER_REFUND = 'transfer_refund'; + + /** + * Returns a list of transactions that have contributed to the EDD\Vendor\Stripe account + * balance (e.g., charges, transfers, and so forth). The transactions are returned + * in sorted order, with the most recent transactions appearing first. + * + * Note that this endpoint was previously called “Balance history” and used the + * path /v1/balance/history. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\BalanceTransaction> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the balance transaction with the given ID. + * + * Note that this endpoint previously used the path + * /v1/balance/history/:id. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BalanceTransaction + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/BankAccount.php b/libraries/Stripe/lib/BankAccount.php new file mode 100644 index 00000000000..5e3bb02eaf7 --- /dev/null +++ b/libraries/Stripe/lib/BankAccount.php @@ -0,0 +1,171 @@ +Customer objects. + * + * On the other hand External Accounts are transfer + * destinations on Account objects for connected accounts. + * They can be bank accounts or debit cards as well, and are documented in the links above. + * + * Related guide: Bank debits and transfers + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string|\EDD\Vendor\Stripe\Account $account The ID of the account that the bank account is associated with. + * @property null|string $account_holder_name The name of the person or business that owns the bank account. + * @property null|string $account_holder_type The type of entity that holds the account. This can be either individual or company. + * @property null|string $account_type The bank account type. This can only be checking or savings in most countries. In Japan, this can only be futsu or toza. + * @property null|string[] $available_payout_methods A set of available payout methods for this bank account. Only values from this set should be passed as the method when creating a payout. + * @property null|string $bank_name Name of the bank associated with the routing number (e.g., WELLS FARGO). + * @property string $country Two-letter ISO code representing the country the bank account is located in. + * @property string $currency Three-letter ISO code for the currency paid out to the bank account. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The ID of the customer that the bank account is associated with. + * @property null|bool $default_for_currency Whether this bank account is the default external account for its currency. + * @property null|string $fingerprint Uniquely identifies this particular bank account. You can use this attribute to check whether two bank accounts are the same. + * @property null|\EDD\Vendor\Stripe\StripeObject $future_requirements Information about the upcoming new requirements for the bank account, including what information needs to be collected, and by when. + * @property string $last4 The last four digits of the bank account number. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $requirements Information about the requirements for the bank account, including what information needs to be collected. + * @property null|string $routing_number The routing transit number for the bank account. + * @property string $status

    For bank accounts, possible values are new, validated, verified, verification_failed, or errored. A bank account that hasn't had any activity or validation performed is new. If EDD\Vendor\Stripe can determine that the bank account exists, its status will be validated. Note that there often isn’t enough information to know (e.g., for smaller credit unions), and the validation is not always run. If customer bank account verification has succeeded, the bank account status will be verified. If the verification failed for any reason, such as microdeposit failure, the status will be verification_failed. If a payout sent to this bank account fails, we'll set the status to errored and will not continue to send scheduled payouts until the bank details are updated.

    For external accounts, possible values are new, errored and verification_failed. If a payout fails, the status is set to errored and scheduled payouts are stopped until account details are updated. In the US and India, if we can't verify the owner of the bank account, we'll set the status to verification_failed. Other validations aren't run against external accounts because they're only used for payouts. This means the other statuses don't apply.

    + */ +class BankAccount extends ApiResource +{ + const OBJECT_NAME = 'bank_account'; + + /** + * Delete a specified external account for a given account. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Possible string representations of the bank verification status. + * + * @see https://stripe.com/docs/api/external_account_bank_accounts/object#account_bank_account_object-status + */ + const STATUS_NEW = 'new'; + const STATUS_VALIDATED = 'validated'; + const STATUS_VERIFIED = 'verified'; + const STATUS_VERIFICATION_FAILED = 'verification_failed'; + const STATUS_ERRORED = 'errored'; + + /** + * @return string The instance URL for this resource. It needs to be special + * cased because it doesn't fit into the standard resource pattern. + */ + public function instanceUrl() + { + if ($this['customer']) { + $base = Customer::classUrl(); + $parent = $this['customer']; + $path = 'sources'; + } elseif ($this['account']) { + $base = Account::classUrl(); + $parent = $this['account']; + $path = 'external_accounts'; + } else { + $msg = 'Bank accounts cannot be accessed without a customer ID or account ID.'; + + throw new Exception\UnexpectedValueException($msg, null); + } + $parentExtn = \urlencode(Util\Util::utf8($parent)); + $extn = \urlencode(Util\Util::utf8($this['id'])); + + return "{$base}/{$parentExtn}/{$path}/{$extn}"; + } + + /** + * @param array|string $_id + * @param null|array|string $_opts + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function retrieve($_id, $_opts = null) + { + $msg = 'Bank accounts cannot be retrieved without a customer ID or ' . + 'an account ID. Retrieve a bank account using ' . + "`Customer::retrieveSource('customer_id', " . + "'bank_account_id')` or `Account::retrieveExternalAccount(" . + "'account_id', 'bank_account_id')`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param string $_id + * @param null|array $_params + * @param null|array|string $_options + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function update($_id, $_params = null, $_options = null) + { + $msg = 'Bank accounts cannot be updated without a customer ID or an ' . + 'account ID. Update a bank account using ' . + "`Customer::updateSource('customer_id', 'bank_account_id', " . + '$updateParams)` or `Account::updateExternalAccount(' . + "'account_id', 'bank_account_id', \$updateParams)`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return static the saved resource + * + * @deprecated The `save` method is deprecated and will be removed in a + * future major version of the library. Use the static method `update` + * on the resource instead. + */ + public function save($opts = null) + { + $params = $this->serializeParameters(); + if (\count($params) > 0) { + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']); + $this->refreshFrom($response, $opts); + } + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return BankAccount the verified bank account + */ + public function verify($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/verify'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/BaseStripeClient.php b/libraries/Stripe/lib/BaseStripeClient.php new file mode 100644 index 00000000000..e83dd17e83d --- /dev/null +++ b/libraries/Stripe/lib/BaseStripeClient.php @@ -0,0 +1,330 @@ + */ + const DEFAULT_CONFIG = [ + 'api_key' => null, + 'app_info' => null, + 'client_id' => null, + 'stripe_account' => null, + 'stripe_version' => \EDD\Vendor\Stripe\Util\ApiVersion::CURRENT, + 'api_base' => self::DEFAULT_API_BASE, + 'connect_base' => self::DEFAULT_CONNECT_BASE, + 'files_base' => self::DEFAULT_FILES_BASE, + ]; + + /** @var array */ + private $config; + + /** @var \EDD\Vendor\Stripe\Util\RequestOptions */ + private $defaultOpts; + + /** + * Initializes a new instance of the {@link BaseStripeClient} class. + * + * The constructor takes a single argument. The argument can be a string, in which case it + * should be the API key. It can also be an array with various configuration settings. + * + * Configuration settings include the following options: + * + * - api_key (null|string): the EDD\Vendor\Stripe API key, to be used in regular API requests. + * - app_info (null|array): information to identify a plugin that integrates EDD\Vendor\Stripe using this library. + * Expects: array{name: string, version?: string, url?: string, partner_id?: string} + * - client_id (null|string): the EDD\Vendor\Stripe client ID, to be used in OAuth requests. + * - stripe_account (null|string): a EDD\Vendor\Stripe account ID. If set, all requests sent by the client + * will automatically use the {@code Stripe-Account} header with that account ID. + * - stripe_version (null|string): a EDD\Vendor\Stripe API version. If set, all requests sent by the client + * will include the {@code Stripe-Version} header with that API version. + * + * The following configuration settings are also available, though setting these should rarely be necessary + * (only useful if you want to send requests to a mock server like stripe-mock): + * + * - api_base (string): the base URL for regular API requests. Defaults to + * {@link DEFAULT_API_BASE}. + * - connect_base (string): the base URL for OAuth requests. Defaults to + * {@link DEFAULT_CONNECT_BASE}. + * - files_base (string): the base URL for file creation requests. Defaults to + * {@link DEFAULT_FILES_BASE}. + * + * @param array|string $config the API key as a string, or an array containing + * the client configuration settings + */ + public function __construct($config = []) + { + if (\is_string($config)) { + $config = ['api_key' => $config]; + } elseif (!\is_array($config)) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('$config must be a string or an array'); + } + + $config = \array_merge(self::DEFAULT_CONFIG, $config); + $this->validateConfig($config); + + $this->config = $config; + + $this->defaultOpts = \EDD\Vendor\Stripe\Util\RequestOptions::parse([ + 'stripe_account' => $config['stripe_account'], + 'stripe_version' => $config['stripe_version'], + ]); + } + + /** + * Gets the API key used by the client to send requests. + * + * @return null|string the API key used by the client to send requests + */ + public function getApiKey() + { + return $this->config['api_key']; + } + + /** + * Gets the client ID used by the client in OAuth requests. + * + * @return null|string the client ID used by the client in OAuth requests + */ + public function getClientId() + { + return $this->config['client_id']; + } + + /** + * Gets the base URL for Stripe's API. + * + * @return string the base URL for Stripe's API + */ + public function getApiBase() + { + return $this->config['api_base']; + } + + /** + * Gets the base URL for Stripe's OAuth API. + * + * @return string the base URL for Stripe's OAuth API + */ + public function getConnectBase() + { + return $this->config['connect_base']; + } + + /** + * Gets the base URL for Stripe's Files API. + * + * @return string the base URL for Stripe's Files API + */ + public function getFilesBase() + { + return $this->config['files_base']; + } + + /** + * Gets the app info for this client. + * + * @return null|array information to identify a plugin that integrates EDD\Vendor\Stripe using this library + */ + public function getAppInfo() + { + return $this->config['app_info']; + } + + /** + * Sends a request to Stripe's API. + * + * @param 'delete'|'get'|'post' $method the HTTP method + * @param string $path the path of the request + * @param array $params the parameters of the request + * @param array|\EDD\Vendor\Stripe\Util\RequestOptions $opts the special modifiers of the request + * + * @return \EDD\Vendor\Stripe\StripeObject the object returned by Stripe's API + */ + public function request($method, $path, $params, $opts) + { + $opts = $this->defaultOpts->merge($opts, true); + $baseUrl = $opts->apiBase ?: $this->getApiBase(); + $requestor = new \EDD\Vendor\Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl, $this->getAppInfo()); + list($response, $opts->apiKey) = $requestor->request($method, $path, $params, $opts->headers, ['stripe_client']); + $opts->discardNonPersistentHeaders(); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Sends a request to Stripe's API, passing chunks of the streamed response + * into a user-provided $readBodyChunkCallable callback. + * + * @param 'delete'|'get'|'post' $method the HTTP method + * @param string $path the path of the request + * @param callable $readBodyChunkCallable a function that will be called + * @param array $params the parameters of the request + * @param array|\EDD\Vendor\Stripe\Util\RequestOptions $opts the special modifiers of the request + * with chunks of bytes from the body if the request is successful + */ + public function requestStream($method, $path, $readBodyChunkCallable, $params, $opts) + { + $opts = $this->defaultOpts->merge($opts, true); + $baseUrl = $opts->apiBase ?: $this->getApiBase(); + $requestor = new \EDD\Vendor\Stripe\ApiRequestor($this->apiKeyForRequest($opts), $baseUrl, $this->getAppInfo()); + list($response, $opts->apiKey) = $requestor->requestStream($method, $path, $readBodyChunkCallable, $params, $opts->headers, ['stripe_client']); + } + + /** + * Sends a request to Stripe's API. + * + * @param 'delete'|'get'|'post' $method the HTTP method + * @param string $path the path of the request + * @param array $params the parameters of the request + * @param array|\EDD\Vendor\Stripe\Util\RequestOptions $opts the special modifiers of the request + * + * @return \EDD\Vendor\Stripe\Collection of ApiResources + */ + public function requestCollection($method, $path, $params, $opts) + { + $obj = $this->request($method, $path, $params, $opts); + if (!($obj instanceof \EDD\Vendor\Stripe\Collection)) { + $received_class = \get_class($obj); + $msg = "Expected to receive `EDD\Vendor\Stripe\\Collection` object from EDD\Vendor\Stripe API. Instead received `{$received_class}`."; + + throw new \EDD\Vendor\Stripe\Exception\UnexpectedValueException($msg); + } + $obj->setFilters($params); + + return $obj; + } + + /** + * Sends a request to Stripe's API. + * + * @param 'delete'|'get'|'post' $method the HTTP method + * @param string $path the path of the request + * @param array $params the parameters of the request + * @param array|\EDD\Vendor\Stripe\Util\RequestOptions $opts the special modifiers of the request + * + * @return \EDD\Vendor\Stripe\SearchResult of ApiResources + */ + public function requestSearchResult($method, $path, $params, $opts) + { + $obj = $this->request($method, $path, $params, $opts); + if (!($obj instanceof \EDD\Vendor\Stripe\SearchResult)) { + $received_class = \get_class($obj); + $msg = "Expected to receive `EDD\Vendor\Stripe\\SearchResult` object from EDD\Vendor\Stripe API. Instead received `{$received_class}`."; + + throw new \EDD\Vendor\Stripe\Exception\UnexpectedValueException($msg); + } + $obj->setFilters($params); + + return $obj; + } + + /** + * @param \EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\AuthenticationException + * + * @return string + */ + private function apiKeyForRequest($opts) + { + $apiKey = $opts->apiKey ?: $this->getApiKey(); + + if (null === $apiKey) { + $msg = 'No API key provided. Set your API key when constructing the ' + . 'StripeClient instance, or provide it on a per-request basis ' + . 'using the `api_key` key in the $opts argument.'; + + throw new \EDD\Vendor\Stripe\Exception\AuthenticationException($msg); + } + + return $apiKey; + } + + /** + * @param array $config + * + * @throws \EDD\Vendor\Stripe\Exception\InvalidArgumentException + */ + private function validateConfig($config) + { + // api_key + if (null !== $config['api_key'] && !\is_string($config['api_key'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('api_key must be null or a string'); + } + + if (null !== $config['api_key'] && ('' === $config['api_key'])) { + $msg = 'api_key cannot be the empty string'; + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException($msg); + } + + if (null !== $config['api_key'] && (\preg_match('/\s/', $config['api_key']))) { + $msg = 'api_key cannot contain whitespace'; + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException($msg); + } + + // client_id + if (null !== $config['client_id'] && !\is_string($config['client_id'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('client_id must be null or a string'); + } + + // stripe_account + if (null !== $config['stripe_account'] && !\is_string($config['stripe_account'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('stripe_account must be null or a string'); + } + + // stripe_version + if (null !== $config['stripe_version'] && !\is_string($config['stripe_version'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('stripe_version must be null or a string'); + } + + // api_base + if (!\is_string($config['api_base'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('api_base must be a string'); + } + + // connect_base + if (!\is_string($config['connect_base'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('connect_base must be a string'); + } + + // files_base + if (!\is_string($config['files_base'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('files_base must be a string'); + } + + // app info + if (null !== $config['app_info'] && !\is_array($config['app_info'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('app_info must be an array'); + } + + $appInfoKeys = ['name', 'version', 'url', 'partner_id']; + if (null !== $config['app_info'] && array_diff_key($config['app_info'], array_flip($appInfoKeys))) { + $msg = 'app_info must be of type array{name: string, version?: string, url?: string, partner_id?: string}'; + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException($msg); + } + + // check absence of extra keys + $extraConfigKeys = \array_diff(\array_keys($config), \array_keys(self::DEFAULT_CONFIG)); + if (!empty($extraConfigKeys)) { + // Wrap in single quote to more easily catch trailing spaces errors + $invalidKeys = "'" . \implode("', '", $extraConfigKeys) . "'"; + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('Found unknown key(s) in configuration array: ' . $invalidKeys); + } + } +} diff --git a/libraries/Stripe/lib/BaseStripeClientInterface.php b/libraries/Stripe/lib/BaseStripeClientInterface.php new file mode 100644 index 00000000000..b76974d128e --- /dev/null +++ b/libraries/Stripe/lib/BaseStripeClientInterface.php @@ -0,0 +1,44 @@ +customer. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $status Status of the alert. This can be active, inactive or archived. + * @property string $title Title of the alert. + * @property null|\EDD\Vendor\Stripe\StripeObject $usage_threshold_config Encapsulates configuration of the alert to monitor usage on a specific Billing Meter. + */ +class Alert extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'billing.alert'; + + const STATUS_ACTIVE = 'active'; + const STATUS_ARCHIVED = 'archived'; + const STATUS_INACTIVE = 'inactive'; + + /** + * Creates a billing alert. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Lists billing active and inactive alerts. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Billing\Alert> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a billing alert given an ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert the activated alert + */ + public function activate($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/activate'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert the archived alert + */ + public function archive($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/archive'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert the deactivated alert + */ + public function deactivate($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/deactivate'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Billing/AlertTriggered.php b/libraries/Stripe/lib/Billing/AlertTriggered.php new file mode 100644 index 00000000000..abce9ec7ad4 --- /dev/null +++ b/libraries/Stripe/lib/Billing/AlertTriggered.php @@ -0,0 +1,18 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property int $value The value triggering the alert + */ +class AlertTriggered extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'billing.alert_triggered'; +} diff --git a/libraries/Stripe/lib/Billing/Meter.php b/libraries/Stripe/lib/Billing/Meter.php new file mode 100644 index 00000000000..64d2be8262e --- /dev/null +++ b/libraries/Stripe/lib/Billing/Meter.php @@ -0,0 +1,167 @@ +event_name field on meter events. + * @property null|string $event_time_window The time window to pre-aggregate meter events for, if any. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $status The meter's status. + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property int $updated Time at which the object was last updated. Measured in seconds since the Unix epoch. + * @property \EDD\Vendor\Stripe\StripeObject $value_settings + */ +class Meter extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'billing.meter'; + + use \EDD\Vendor\Stripe\ApiOperations\NestedResource; + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const EVENT_TIME_WINDOW_DAY = 'day'; + const EVENT_TIME_WINDOW_HOUR = 'hour'; + + const STATUS_ACTIVE = 'active'; + const STATUS_INACTIVE = 'inactive'; + + /** + * Creates a billing meter. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Retrieve a list of billing meters. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Billing\Meter> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a billing meter given an ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a billing meter. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter the deactivated meter + */ + public function deactivate($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/deactivate'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter the reactivated meter + */ + public function reactivate($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/reactivate'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + const PATH_EVENT_SUMMARIES = '/event_summaries'; + + /** + * @param string $id the ID of the meter on which to retrieve the meter event summaries + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Billing\MeterEventSummary> the list of meter event summaries + */ + public static function allEventSummaries($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_EVENT_SUMMARIES, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Billing/MeterEvent.php b/libraries/Stripe/lib/Billing/MeterEvent.php new file mode 100644 index 00000000000..1e4a6b9d865 --- /dev/null +++ b/libraries/Stripe/lib/Billing/MeterEvent.php @@ -0,0 +1,44 @@ +event_name field on a meter. + * @property string $identifier A unique identifier for the event. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $payload The payload of the event. This contains the fields corresponding to a meter's customer_mapping.event_payload_key (default is stripe_customer_id) and value_settings.event_payload_key (default is value). Read more about the payload. + * @property int $timestamp The timestamp passed in when creating the event. Measured in seconds since the Unix epoch. + */ +class MeterEvent extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'billing.meter_event'; + + /** + * Creates a billing meter event. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\MeterEvent the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Billing/MeterEventAdjustment.php b/libraries/Stripe/lib/Billing/MeterEventAdjustment.php new file mode 100644 index 00000000000..175746f78eb --- /dev/null +++ b/libraries/Stripe/lib/Billing/MeterEventAdjustment.php @@ -0,0 +1,45 @@ +event_name field on a meter. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $status The meter event adjustment's status. + * @property string $type Specifies whether to cancel a single event or a range of events for a time period. Time period cancellation is not supported yet. + */ +class MeterEventAdjustment extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'billing.meter_event_adjustment'; + + const STATUS_COMPLETE = 'complete'; + const STATUS_PENDING = 'pending'; + + /** + * Creates a billing meter event adjustment. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\MeterEventAdjustment the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Billing/MeterEventSummary.php b/libraries/Stripe/lib/Billing/MeterEventSummary.php new file mode 100644 index 00000000000..0e55a409956 --- /dev/null +++ b/libraries/Stripe/lib/Billing/MeterEventSummary.php @@ -0,0 +1,22 @@ +start_time (inclusive) and end_time (inclusive). The aggregation strategy is defined on meter via default_aggregation. + * @property int $end_time End timestamp for this event summary (exclusive). Must be aligned with minute boundaries. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $meter The meter associated with this event summary. + * @property int $start_time Start timestamp for this event summary (inclusive). Must be aligned with minute boundaries. + */ +class MeterEventSummary extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'billing.meter_event_summary'; +} diff --git a/libraries/Stripe/lib/BillingPortal/Configuration.php b/libraries/Stripe/lib/BillingPortal/Configuration.php new file mode 100644 index 00000000000..86712052d99 --- /dev/null +++ b/libraries/Stripe/lib/BillingPortal/Configuration.php @@ -0,0 +1,113 @@ +overriden when creating the session. + * @property \EDD\Vendor\Stripe\StripeObject $features + * @property bool $is_default Whether the configuration is the default. If true, this configuration can be managed in the Dashboard and portal sessions will use this configuration unless it is overriden when creating the session. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $login_page + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property int $updated Time at which the object was last updated. Measured in seconds since the Unix epoch. + */ +class Configuration extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'billing_portal.configuration'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + /** + * Creates a configuration that describes the functionality and behavior of a + * PortalSession. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BillingPortal\Configuration the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of configurations that describe the functionality of the customer + * portal. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\BillingPortal\Configuration> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a configuration that describes the functionality of the customer + * portal. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BillingPortal\Configuration + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a configuration that describes the functionality of the customer portal. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BillingPortal\Configuration the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/BillingPortal/Session.php b/libraries/Stripe/lib/BillingPortal/Session.php new file mode 100644 index 00000000000..62e011f8876 --- /dev/null +++ b/libraries/Stripe/lib/BillingPortal/Session.php @@ -0,0 +1,60 @@ +Customer management + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string|\EDD\Vendor\Stripe\BillingPortal\Configuration $configuration The configuration used by this session, describing the features available. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $customer The ID of the customer for this session. + * @property null|\EDD\Vendor\Stripe\StripeObject $flow Information about a specific flow for the customer to go through. See the docs to learn more about using customer portal deep links and flows. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $locale The IETF language tag of the locale Customer Portal is displayed in. If blank or auto, the customer’s preferred_locales or browser’s locale is used. + * @property null|string $on_behalf_of The account for which the session was created on behalf of. When specified, only subscriptions and invoices with this on_behalf_of account appear in the portal. For more information, see the docs. Use the Accounts API to modify the on_behalf_of account's branding settings, which the portal displays. + * @property null|string $return_url The URL to redirect customers to when they click on the portal's link to return to your website. + * @property string $url The short-lived URL of the session that gives customers access to the customer portal. + */ +class Session extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'billing_portal.session'; + + /** + * Creates a session of the customer portal. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BillingPortal\Session the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Capability.php b/libraries/Stripe/lib/Capability.php new file mode 100644 index 00000000000..4225713f3eb --- /dev/null +++ b/libraries/Stripe/lib/Capability.php @@ -0,0 +1,107 @@ +Account capabilities + * + * @property string $id The identifier for the capability. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string|\EDD\Vendor\Stripe\Account $account The account for which the capability enables functionality. + * @property null|\EDD\Vendor\Stripe\StripeObject $future_requirements + * @property bool $requested Whether the capability has been requested. + * @property null|int $requested_at Time at which the capability was requested. Measured in seconds since the Unix epoch. + * @property null|\EDD\Vendor\Stripe\StripeObject $requirements + * @property string $status The status of the capability. Can be active, inactive, pending, or unrequested. + */ +class Capability extends ApiResource +{ + const OBJECT_NAME = 'capability'; + + const STATUS_ACTIVE = 'active'; + const STATUS_INACTIVE = 'inactive'; + const STATUS_PENDING = 'pending'; + const STATUS_UNREQUESTED = 'unrequested'; + + /** + * @return string the API URL for this EDD\Vendor\Stripe account reversal + */ + public function instanceUrl() + { + $id = $this['id']; + $account = $this['account']; + if (!$id) { + throw new Exception\UnexpectedValueException( + 'Could not determine which URL to request: ' . + "class instance has invalid ID: {$id}", + null + ); + } + $id = Util\Util::utf8($id); + $account = Util\Util::utf8($account); + + $base = Account::classUrl(); + $accountExtn = \urlencode($account); + $extn = \urlencode($id); + + return "{$base}/{$accountExtn}/capabilities/{$extn}"; + } + + /** + * @param array|string $_id + * @param null|array|string $_opts + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function retrieve($_id, $_opts = null) + { + $msg = 'Capabilities cannot be retrieved without an account ID. ' . + 'Retrieve a capability using `Account::retrieveCapability(' . + "'account_id', 'capability_id')`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param string $_id + * @param null|array $_params + * @param null|array|string $_options + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function update($_id, $_params = null, $_options = null) + { + $msg = 'Capabilities cannot be updated without an account ID. ' . + 'Update a capability using `Account::updateCapability(' . + "'account_id', 'capability_id', \$updateParams)`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return static the saved resource + * + * @deprecated The `save` method is deprecated and will be removed in a + * future major version of the library. Use the static method `update` + * on the resource instead. + */ + public function save($opts = null) + { + $params = $this->serializeParameters(); + if (\count($params) > 0) { + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']); + $this->refreshFrom($response, $opts); + } + + return $this; + } +} diff --git a/libraries/Stripe/lib/Card.php b/libraries/Stripe/lib/Card.php new file mode 100644 index 00000000000..d1ae706e69f --- /dev/null +++ b/libraries/Stripe/lib/Card.php @@ -0,0 +1,179 @@ +Card payments with Sources + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string|\EDD\Vendor\Stripe\Account $account The account this card belongs to. This attribute will not be in the card object if the card belongs to a customer or recipient instead. This property is only available for accounts where controller.requirement_collection is application, which includes Custom accounts. + * @property null|string $address_city City/District/Suburb/Town/Village. + * @property null|string $address_country Billing address country, if provided when creating card. + * @property null|string $address_line1 Address line 1 (Street address/PO Box/Company name). + * @property null|string $address_line1_check If address_line1 was provided, results of the check: pass, fail, unavailable, or unchecked. + * @property null|string $address_line2 Address line 2 (Apartment/Suite/Unit/Building). + * @property null|string $address_state State/County/Province/Region. + * @property null|string $address_zip ZIP or postal code. + * @property null|string $address_zip_check If address_zip was provided, results of the check: pass, fail, unavailable, or unchecked. + * @property null|string[] $available_payout_methods A set of available payout methods for this card. Only values from this set should be passed as the method when creating a payout. + * @property string $brand Card brand. Can be American Express, Diners Club, Discover, Eftpos Australia, Girocard, JCB, MasterCard, UnionPay, Visa, or Unknown. + * @property null|string $country Two-letter ISO code representing the country of the card. You could use this attribute to get a sense of the international breakdown of cards you've collected. + * @property null|string $currency Three-letter ISO code for currency in lowercase. Must be a supported currency. Only applicable on accounts (not customers or recipients). The card can be used as a transfer destination for funds in this currency. This property is only available for accounts where controller.requirement_collection is application, which includes Custom accounts. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The customer that this card belongs to. This attribute will not be in the card object if the card belongs to an account or recipient instead. + * @property null|string $cvc_check If a CVC was provided, results of the check: pass, fail, unavailable, or unchecked. A result of unchecked indicates that CVC was provided but hasn't been checked yet. Checks are typically performed when attaching a card to a Customer object, or when creating a charge. For more details, see Check if a card is valid without a charge. + * @property null|bool $default_for_currency Whether this card is the default external account for its currency. This property is only available for accounts where controller.requirement_collection is application, which includes Custom accounts. + * @property null|string $dynamic_last4 (For tokenized numbers only.) The last four digits of the device account number. + * @property int $exp_month Two-digit number representing the card's expiration month. + * @property int $exp_year Four-digit number representing the card's expiration year. + * @property null|string $fingerprint

    Uniquely identifies this particular card number. You can use this attribute to check whether two customers who’ve signed up with you are using the same card number, for example. For payment methods that tokenize card information (Apple Pay, Google Pay), the tokenized number might be provided instead of the underlying card number.

    As of May 1, 2021, card fingerprint in India for Connect changed to allow two fingerprints for the same card---one for India and one for the rest of the world.

    + * @property string $funding Card funding type. Can be credit, debit, prepaid, or unknown. + * @property string $last4 The last four digits of the card. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $name Cardholder name. + * @property null|\EDD\Vendor\Stripe\StripeObject $networks + * @property null|string $status For external accounts that are cards, possible values are new and errored. If a payout fails, the status is set to errored and scheduled payouts are stopped until account details are updated. + * @property null|string $tokenization_method If the card number is tokenized, this is the method that was used. Can be android_pay (includes Google Pay), apple_pay, masterpass, visa_checkout, or null. + */ +class Card extends ApiResource +{ + const OBJECT_NAME = 'card'; + + /** + * Delete a specified external account for a given account. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Card the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Possible string representations of the CVC check status. + * + * @see https://stripe.com/docs/api/cards/object#card_object-cvc_check + */ + const CVC_CHECK_FAIL = 'fail'; + const CVC_CHECK_PASS = 'pass'; + const CVC_CHECK_UNAVAILABLE = 'unavailable'; + const CVC_CHECK_UNCHECKED = 'unchecked'; + + /** + * Possible string representations of the funding of the card. + * + * @see https://stripe.com/docs/api/cards/object#card_object-funding + */ + const FUNDING_CREDIT = 'credit'; + const FUNDING_DEBIT = 'debit'; + const FUNDING_PREPAID = 'prepaid'; + const FUNDING_UNKNOWN = 'unknown'; + + /** + * Possible string representations of the tokenization method when using Apple Pay or Google Pay. + * + * @see https://stripe.com/docs/api/cards/object#card_object-tokenization_method + */ + const TOKENIZATION_METHOD_APPLE_PAY = 'apple_pay'; + const TOKENIZATION_METHOD_GOOGLE_PAY = 'google_pay'; + + /** + * @return string The instance URL for this resource. It needs to be special + * cased because cards are nested resources that may belong to different + * top-level resources. + */ + public function instanceUrl() + { + if ($this['customer']) { + $base = Customer::classUrl(); + $parent = $this['customer']; + $path = 'sources'; + } elseif ($this['account']) { + $base = Account::classUrl(); + $parent = $this['account']; + $path = 'external_accounts'; + } else { + $msg = 'Cards cannot be accessed without a customer ID, or account ID.'; + + throw new Exception\UnexpectedValueException($msg); + } + $parentExtn = \urlencode(Util\Util::utf8($parent)); + $extn = \urlencode(Util\Util::utf8($this['id'])); + + return "{$base}/{$parentExtn}/{$path}/{$extn}"; + } + + /** + * @param array|string $_id + * @param null|array|string $_opts + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function retrieve($_id, $_opts = null) + { + $msg = 'Cards cannot be retrieved without a customer ID or an ' . + 'account ID. Retrieve a card using ' . + "`Customer::retrieveSource('customer_id', 'card_id')` or " . + "`Account::retrieveExternalAccount('account_id', 'card_id')`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param string $_id + * @param null|array $_params + * @param null|array|string $_options + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function update($_id, $_params = null, $_options = null) + { + $msg = 'Cards cannot be updated without a customer ID or an ' . + 'account ID. Update a card using ' . + "`Customer::updateSource('customer_id', 'card_id', " . + '$updateParams)` or `Account::updateExternalAccount(' . + "'account_id', 'card_id', \$updateParams)`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return static the saved resource + * + * @deprecated The `save` method is deprecated and will be removed in a + * future major version of the library. Use the static method `update` + * on the resource instead. + */ + public function save($opts = null) + { + $params = $this->serializeParameters(); + if (\count($params) > 0) { + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']); + $this->refreshFrom($response, $opts); + } + + return $this; + } +} diff --git a/libraries/Stripe/lib/CashBalance.php b/libraries/Stripe/lib/CashBalance.php new file mode 100644 index 00000000000..8579fa61187 --- /dev/null +++ b/libraries/Stripe/lib/CashBalance.php @@ -0,0 +1,64 @@ +Cash balance represents real funds. Customers can add funds to their cash balance by sending a bank transfer. These funds can be used for payment and can eventually be paid out to your bank account. + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $available A hash of all cash balances available to this customer. You cannot delete a customer with any cash balances, even if the balance is 0. Amounts are represented in the smallest currency unit. + * @property string $customer The ID of the customer whose cash balance this object represents. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $settings + */ +class CashBalance extends ApiResource +{ + const OBJECT_NAME = 'cash_balance'; + + /** + * @return string the API URL for this balance transaction + */ + public function instanceUrl() + { + $customer = $this['customer']; + $customer = Util\Util::utf8($customer); + + $base = Customer::classUrl(); + $customerExtn = \urlencode($customer); + + return "{$base}/{$customerExtn}/cash_balance"; + } + + /** + * @param array|string $_id + * @param null|array|string $_opts + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function retrieve($_id, $_opts = null) + { + $msg = 'Customer Cash Balance cannot be retrieved without a ' . + 'customer ID. Retrieve a Customer Cash Balance using ' . + "`Customer::retrieveCashBalance('customer_id')`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param string $_id + * @param null|array $_params + * @param null|array|string $_options + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function update($_id, $_params = null, $_options = null) + { + $msg = 'Customer Cash Balance cannot be updated without a ' . + 'customer ID. Retrieve a Customer Cash Balance using ' . + "`Customer::updateCashBalance('customer_id')`."; + + throw new Exception\BadMethodCallException($msg); + } +} diff --git a/libraries/Stripe/lib/Charge.php b/libraries/Stripe/lib/Charge.php new file mode 100644 index 00000000000..90bbbc722fe --- /dev/null +++ b/libraries/Stripe/lib/Charge.php @@ -0,0 +1,277 @@ +Charge object represents a single attempt to move money into your EDD\Vendor\Stripe account. + * PaymentIntent confirmation is the most common way to create Charges, but transferring + * money to a different EDD\Vendor\Stripe account through Connect also creates Charges. + * Some legacy payment flows create Charges directly, which is not recommended for new integrations. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount intended to be collected by this payment. A positive integer representing how much to charge in the smallest currency unit (e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal currency). The minimum amount is $0.50 US or equivalent in charge currency. The amount value supports up to eight digits (e.g., a value of 99999999 for a USD charge of $999,999.99). + * @property int $amount_captured Amount in cents (or local equivalent) captured (can be less than the amount attribute on the charge if a partial capture was made). + * @property int $amount_refunded Amount in cents (or local equivalent) refunded (can be less than the amount attribute on the charge if a partial refund was issued). + * @property null|string|\EDD\Vendor\Stripe\Application $application ID of the Connect application that created the charge. + * @property null|string|\EDD\Vendor\Stripe\ApplicationFee $application_fee The application fee (if any) for the charge. See the Connect documentation for details. + * @property null|int $application_fee_amount The amount of the application fee (if any) requested for the charge. See the Connect documentation for details. + * @property null|string $authorization_code Authorization code on the charge. + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $balance_transaction ID of the balance transaction that describes the impact of this charge on your account balance (not including refunds or disputes). + * @property \EDD\Vendor\Stripe\StripeObject $billing_details + * @property null|string $calculated_statement_descriptor The full statement descriptor that is passed to card networks, and that is displayed on your customers' credit card and bank statements. Allows you to see what the statement descriptor looks like after the static and dynamic portions are combined. This value only exists for card payments. + * @property bool $captured If the charge was created without capturing, this Boolean represents whether it is still uncaptured or has since been captured. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer ID of the customer this charge is for if one exists. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property bool $disputed Whether the charge has been disputed. + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $failure_balance_transaction ID of the balance transaction that describes the reversal of the balance on your account due to payment failure. + * @property null|string $failure_code Error code explaining reason for charge failure if available (see the errors section for a list of codes). + * @property null|string $failure_message Message to user further explaining reason for charge failure if available. + * @property null|\EDD\Vendor\Stripe\StripeObject $fraud_details Information on fraud assessments for the charge. + * @property null|string|\EDD\Vendor\Stripe\Invoice $invoice ID of the invoice this charge is for if one exists. + * @property null|\EDD\Vendor\Stripe\StripeObject $level3 + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string|\EDD\Vendor\Stripe\Account $on_behalf_of The account (if any) the charge was made on behalf of without triggering an automatic transfer. See the Connect documentation for details. + * @property null|\EDD\Vendor\Stripe\StripeObject $outcome Details about whether the payment was accepted, and why. See understanding declines for details. + * @property bool $paid true if the charge succeeded, or was successfully authorized for later capture. + * @property null|string|\EDD\Vendor\Stripe\PaymentIntent $payment_intent ID of the PaymentIntent associated with this charge, if one exists. + * @property null|string $payment_method ID of the payment method used in this charge. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_details Details about the payment method at the time of the transaction. + * @property null|\EDD\Vendor\Stripe\StripeObject $radar_options Options to configure Radar. See Radar Session for more information. + * @property null|string $receipt_email This is the email address that the receipt for this charge was sent to. + * @property null|string $receipt_number This is the transaction number that appears on email receipts sent for this charge. This attribute will be null until a receipt has been sent. + * @property null|string $receipt_url This is the URL to view the receipt for this charge. The receipt is kept up-to-date to the latest state of the charge, including any refunds. If the charge is for an Invoice, the receipt will be stylized as an Invoice receipt. + * @property bool $refunded Whether the charge has been fully refunded. If the charge is only partially refunded, this attribute will still be false. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Refund> $refunds A list of refunds that have been applied to the charge. + * @property null|string|\EDD\Vendor\Stripe\Review $review ID of the review associated with this charge if one exists. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping Shipping information for the charge. + * @property null|\EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source $source This is a legacy field that will be removed in the future. It contains the Source, Card, or BankAccount object used for the charge. For details about the payment method used for this charge, refer to payment_method or payment_method_details instead. + * @property null|string|\EDD\Vendor\Stripe\Transfer $source_transfer The transfer ID which created this charge. Only present if the charge came from another EDD\Vendor\Stripe account. See the Connect documentation for details. + * @property null|string $statement_descriptor

    For a non-card charge, text that appears on the customer's statement as the statement descriptor. This value overrides the account's default statement descriptor. For information about requirements, including the 22-character limit, see the Statement Descriptor docs.

    For a card charge, this value is ignored unless you don't specify a statement_descriptor_suffix, in which case this value is used as the suffix.

    + * @property null|string $statement_descriptor_suffix Provides information about a card charge. Concatenated to the account's statement descriptor prefix to form the complete statement descriptor that appears on the customer's statement. If the account has no prefix value, the suffix is concatenated to the account's statement descriptor. + * @property string $status The status of the payment is either succeeded, pending, or failed. + * @property null|string|\EDD\Vendor\Stripe\Transfer $transfer ID of the transfer to the destination account (only applicable if the charge was created using the destination parameter). + * @property null|\EDD\Vendor\Stripe\StripeObject $transfer_data An optional dictionary including the account to automatically transfer to as part of a destination charge. See the Connect documentation for details. + * @property null|string $transfer_group A string that identifies this transaction as part of a group. See the Connect documentation for details. + */ +class Charge extends ApiResource +{ + const OBJECT_NAME = 'charge'; + + use ApiOperations\NestedResource; + use ApiOperations\Update; + + const STATUS_FAILED = 'failed'; + const STATUS_PENDING = 'pending'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * This method is no longer recommended—use the Payment Intents API to initiate a new + * payment instead. Confirmation of the PaymentIntent creates the + * Charge object used to request payment. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Charge the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of charges you’ve previously created. The charges are returned in + * sorted order, with the most recent charges appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Charge> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a charge that has previously been created. Supply the + * unique charge ID that was returned from your previous request, and EDD\Vendor\Stripe will + * return the corresponding charge information. The same information is returned + * when creating or refunding the charge. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Charge + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified charge by setting the values of the parameters passed. Any + * parameters not provided will be left unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Charge the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Possible string representations of decline codes. + * These strings are applicable to the decline_code property of the \EDD\Vendor\Stripe\Exception\CardException exception. + * + * @see https://stripe.com/docs/declines/codes + */ + const DECLINED_AUTHENTICATION_REQUIRED = 'authentication_required'; + const DECLINED_APPROVE_WITH_ID = 'approve_with_id'; + const DECLINED_CALL_ISSUER = 'call_issuer'; + const DECLINED_CARD_NOT_SUPPORTED = 'card_not_supported'; + const DECLINED_CARD_VELOCITY_EXCEEDED = 'card_velocity_exceeded'; + const DECLINED_CURRENCY_NOT_SUPPORTED = 'currency_not_supported'; + const DECLINED_DO_NOT_HONOR = 'do_not_honor'; + const DECLINED_DO_NOT_TRY_AGAIN = 'do_not_try_again'; + const DECLINED_DUPLICATED_TRANSACTION = 'duplicate_transaction'; + const DECLINED_EXPIRED_CARD = 'expired_card'; + const DECLINED_FRAUDULENT = 'fraudulent'; + const DECLINED_GENERIC_DECLINE = 'generic_decline'; + const DECLINED_INCORRECT_NUMBER = 'incorrect_number'; + const DECLINED_INCORRECT_CVC = 'incorrect_cvc'; + const DECLINED_INCORRECT_PIN = 'incorrect_pin'; + const DECLINED_INCORRECT_ZIP = 'incorrect_zip'; + const DECLINED_INSUFFICIENT_FUNDS = 'insufficient_funds'; + const DECLINED_INVALID_ACCOUNT = 'invalid_account'; + const DECLINED_INVALID_AMOUNT = 'invalid_amount'; + const DECLINED_INVALID_CVC = 'invalid_cvc'; + const DECLINED_INVALID_EXPIRY_YEAR = 'invalid_expiry_year'; + const DECLINED_INVALID_NUMBER = 'invalid_number'; + const DECLINED_INVALID_PIN = 'invalid_pin'; + const DECLINED_ISSUER_NOT_AVAILABLE = 'issuer_not_available'; + const DECLINED_LOST_CARD = 'lost_card'; + const DECLINED_MERCHANT_BLACKLIST = 'merchant_blacklist'; + const DECLINED_NEW_ACCOUNT_INFORMATION_AVAILABLE = 'new_account_information_available'; + const DECLINED_NO_ACTION_TAKEN = 'no_action_taken'; + const DECLINED_NOT_PERMITTED = 'not_permitted'; + const DECLINED_OFFLINE_PIN_REQUIRED = 'offline_pin_required'; + const DECLINED_ONLINE_OR_OFFLINE_PIN_REQUIRED = 'online_or_offline_pin_required'; + const DECLINED_PICKUP_CARD = 'pickup_card'; + const DECLINED_PIN_TRY_EXCEEDED = 'pin_try_exceeded'; + const DECLINED_PROCESSING_ERROR = 'processing_error'; + const DECLINED_REENTER_TRANSACTION = 'reenter_transaction'; + const DECLINED_RESTRICTED_CARD = 'restricted_card'; + const DECLINED_REVOCATION_OF_ALL_AUTHORIZATIONS = 'revocation_of_all_authorizations'; + const DECLINED_REVOCATION_OF_AUTHORIZATION = 'revocation_of_authorization'; + const DECLINED_SECURITY_VIOLATION = 'security_violation'; + const DECLINED_SERVICE_NOT_ALLOWED = 'service_not_allowed'; + const DECLINED_STOLEN_CARD = 'stolen_card'; + const DECLINED_STOP_PAYMENT_ORDER = 'stop_payment_order'; + const DECLINED_TESTMODE_DECLINE = 'testmode_decline'; + const DECLINED_TRANSACTION_NOT_ALLOWED = 'transaction_not_allowed'; + const DECLINED_TRY_AGAIN_LATER = 'try_again_later'; + const DECLINED_WITHDRAWAL_COUNT_LIMIT_EXCEEDED = 'withdrawal_count_limit_exceeded'; + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Charge the captured charge + */ + public function capture($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/capture'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Charge> the charge search results + */ + public static function search($params = null, $opts = null) + { + $url = '/v1/charges/search'; + + return static::_requestPage($url, \EDD\Vendor\Stripe\SearchResult::class, $params, $opts); + } + + const PATH_REFUNDS = '/refunds'; + + /** + * @param string $id the ID of the charge on which to retrieve the refunds + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Refund> the list of refunds + */ + public static function allRefunds($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_REFUNDS, $params, $opts); + } + + /** + * @param string $id the ID of the charge to which the refund belongs + * @param string $refundId the ID of the refund to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund + */ + public static function retrieveRefund($id, $refundId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_REFUNDS, $refundId, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Checkout/Session.php b/libraries/Stripe/lib/Checkout/Session.php new file mode 100644 index 00000000000..4d19d7f5035 --- /dev/null +++ b/libraries/Stripe/lib/Checkout/Session.php @@ -0,0 +1,234 @@ +Checkout + * or Payment Links. We recommend creating a + * new Session each time your customer attempts to pay. + * + * Once payment is successful, the Checkout Session will contain a reference + * to the Customer, and either the successful + * PaymentIntent or an active + * Subscription. + * + * You can create a Checkout Session on your server and redirect to its URL + * to begin Checkout. + * + * Related guide: Checkout quickstart + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $after_expiration When set, provides configuration for actions to take if this Checkout Session expires. + * @property null|bool $allow_promotion_codes Enables user redeemable promotion codes. + * @property null|int $amount_subtotal Total of all items before discounts or taxes are applied. + * @property null|int $amount_total Total of all items after discounts and taxes are applied. + * @property \EDD\Vendor\Stripe\StripeObject $automatic_tax + * @property null|string $billing_address_collection Describes whether Checkout should collect the customer's billing address. Defaults to auto. + * @property null|string $cancel_url If set, Checkout displays a back button and customers will be directed to this URL if they decide to cancel payment and return to your website. + * @property null|string $client_reference_id A unique string to reference the Checkout Session. This can be a customer ID, a cart ID, or similar, and can be used to reconcile the Session with your internal systems. + * @property null|string $client_secret Client secret to be used when initializing Stripe.js embedded checkout. + * @property null|\EDD\Vendor\Stripe\StripeObject $consent Results of consent_collection for this session. + * @property null|\EDD\Vendor\Stripe\StripeObject $consent_collection When set, provides configuration for the Checkout Session to gather active consent from customers. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|\EDD\Vendor\Stripe\StripeObject $currency_conversion Currency conversion details for Adaptive Pricing sessions + * @property \EDD\Vendor\Stripe\StripeObject[] $custom_fields Collect additional information from your customer using custom fields. Up to 3 fields are supported. + * @property \EDD\Vendor\Stripe\StripeObject $custom_text + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The ID of the customer for this Session. For Checkout Sessions in subscription mode or Checkout Sessions with customer_creation set as always in payment mode, Checkout will create a new customer object based on information provided during the payment flow unless an existing customer was provided when the Session was created. + * @property null|string $customer_creation Configure whether a Checkout Session creates a Customer when the Checkout Session completes. + * @property null|\EDD\Vendor\Stripe\StripeObject $customer_details The customer details including the customer's tax exempt status and the customer's tax IDs. Customer's address details are not present on Sessions in setup mode. + * @property null|string $customer_email If provided, this value will be used when the Customer object is created. If not provided, customers will be asked to enter their email address. Use this parameter to prefill customer data if you already have an email on file. To access information about the customer once the payment flow is complete, use the customer attribute. + * @property int $expires_at The timestamp at which the Checkout Session will expire. + * @property null|string|\EDD\Vendor\Stripe\Invoice $invoice ID of the invoice created by the Checkout Session, if it exists. + * @property null|\EDD\Vendor\Stripe\StripeObject $invoice_creation Details on the state of invoice creation for the Checkout Session. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> $line_items The line items purchased by the customer. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $locale The IETF language tag of the locale Checkout is displayed in. If blank or auto, the browser's locale is used. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $mode The mode of the Checkout Session. + * @property null|string|\EDD\Vendor\Stripe\PaymentIntent $payment_intent The ID of the PaymentIntent for Checkout Sessions in payment mode. You can't confirm or cancel the PaymentIntent for a Checkout Session. To cancel, expire the Checkout Session instead. + * @property null|string|\EDD\Vendor\Stripe\PaymentLink $payment_link The ID of the Payment Link that created this Session. + * @property null|string $payment_method_collection Configure whether a Checkout Session should collect a payment method. Defaults to always. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_configuration_details Information about the payment method configuration used for this Checkout session if using dynamic payment methods. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_options Payment-method-specific configuration for the PaymentIntent or SetupIntent of this CheckoutSession. + * @property string[] $payment_method_types A list of the types of payment methods (e.g. card) this Checkout Session is allowed to accept. + * @property string $payment_status The payment status of the Checkout Session, one of paid, unpaid, or no_payment_required. You can use this value to decide when to fulfill your customer's order. + * @property null|\EDD\Vendor\Stripe\StripeObject $phone_number_collection + * @property null|string $recovered_from The ID of the original expired Checkout Session that triggered the recovery flow. + * @property null|string $redirect_on_completion This parameter applies to ui_mode: embedded. Learn more about the redirect behavior of embedded sessions. Defaults to always. + * @property null|string $return_url Applies to Checkout Sessions with ui_mode: embedded. The URL to redirect your customer back to after they authenticate or cancel their payment on the payment method's app or site. + * @property null|\EDD\Vendor\Stripe\StripeObject $saved_payment_method_options Controls saved payment method settings for the session. Only available in payment and subscription mode. + * @property null|string|\EDD\Vendor\Stripe\SetupIntent $setup_intent The ID of the SetupIntent for Checkout Sessions in setup mode. You can't confirm or cancel the SetupIntent for a Checkout Session. To cancel, expire the Checkout Session instead. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_address_collection When set, provides configuration for Checkout to collect a shipping address from a customer. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_cost The details of the customer cost of shipping, including the customer chosen ShippingRate. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_details Shipping information for this Checkout Session. + * @property \EDD\Vendor\Stripe\StripeObject[] $shipping_options The shipping rate options applied to this Session. + * @property null|string $status The status of the Checkout Session, one of open, complete, or expired. + * @property null|string $submit_type Describes the type of transaction being performed by Checkout in order to customize relevant text on the page, such as the submit button. submit_type can only be specified on Checkout Sessions in payment mode. If blank or auto, pay is used. + * @property null|string|\EDD\Vendor\Stripe\Subscription $subscription The ID of the subscription for Checkout Sessions in subscription mode. + * @property null|string $success_url The URL the customer will be directed to after the payment or subscription creation is successful. + * @property null|\EDD\Vendor\Stripe\StripeObject $tax_id_collection + * @property null|\EDD\Vendor\Stripe\StripeObject $total_details Tax and discount details for the computed total amount. + * @property null|string $ui_mode The UI mode of the Session. Defaults to hosted. + * @property null|string $url The URL to the Checkout Session. Redirect customers to this URL to take them to Checkout. If you’re using Custom Domains, the URL will use your subdomain. Otherwise, it’ll use checkout.stripe.com. This value is only present when the session is active. + */ +class Session extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'checkout.session'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const BILLING_ADDRESS_COLLECTION_AUTO = 'auto'; + const BILLING_ADDRESS_COLLECTION_REQUIRED = 'required'; + + const CUSTOMER_CREATION_ALWAYS = 'always'; + const CUSTOMER_CREATION_IF_REQUIRED = 'if_required'; + + const MODE_PAYMENT = 'payment'; + const MODE_SETUP = 'setup'; + const MODE_SUBSCRIPTION = 'subscription'; + + const PAYMENT_METHOD_COLLECTION_ALWAYS = 'always'; + const PAYMENT_METHOD_COLLECTION_IF_REQUIRED = 'if_required'; + + const PAYMENT_STATUS_NO_PAYMENT_REQUIRED = 'no_payment_required'; + const PAYMENT_STATUS_PAID = 'paid'; + const PAYMENT_STATUS_UNPAID = 'unpaid'; + + const REDIRECT_ON_COMPLETION_ALWAYS = 'always'; + const REDIRECT_ON_COMPLETION_IF_REQUIRED = 'if_required'; + const REDIRECT_ON_COMPLETION_NEVER = 'never'; + + const STATUS_COMPLETE = 'complete'; + const STATUS_EXPIRED = 'expired'; + const STATUS_OPEN = 'open'; + + const SUBMIT_TYPE_AUTO = 'auto'; + const SUBMIT_TYPE_BOOK = 'book'; + const SUBMIT_TYPE_DONATE = 'donate'; + const SUBMIT_TYPE_PAY = 'pay'; + + const UI_MODE_EMBEDDED = 'embedded'; + const UI_MODE_HOSTED = 'hosted'; + + /** + * Creates a Session object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Checkout\Session the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of Checkout Sessions. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Checkout\Session> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a Session object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Checkout\Session + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a Session object. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Checkout\Session the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Checkout\Session the expired session + */ + public function expire($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/expire'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> list of line items + */ + public static function allLineItems($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/line_items'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Climate/Order.php b/libraries/Stripe/lib/Climate/Order.php new file mode 100644 index 00000000000..5b031aecf6c --- /dev/null +++ b/libraries/Stripe/lib/Climate/Order.php @@ -0,0 +1,150 @@ +Frontier's service fees in the currency's smallest unit. + * @property int $amount_subtotal Total amount of the carbon removal in the currency's smallest unit. + * @property int $amount_total Total amount of the order including fees in the currency's smallest unit. + * @property null|\EDD\Vendor\Stripe\StripeObject $beneficiary + * @property null|int $canceled_at Time at which the order was canceled. Measured in seconds since the Unix epoch. + * @property null|string $cancellation_reason Reason for the cancellation of this order. + * @property null|string $certificate For delivered orders, a URL to a delivery certificate for the order. + * @property null|int $confirmed_at Time at which the order was confirmed. Measured in seconds since the Unix epoch. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase, representing the currency for this order. + * @property null|int $delayed_at Time at which the order's expected_delivery_year was delayed. Measured in seconds since the Unix epoch. + * @property null|int $delivered_at Time at which the order was delivered. Measured in seconds since the Unix epoch. + * @property \EDD\Vendor\Stripe\StripeObject[] $delivery_details Details about the delivery of carbon removal for this order. + * @property int $expected_delivery_year The year this order is expected to be delivered. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $metric_tons Quantity of carbon removal that is included in this order. + * @property string|\EDD\Vendor\Stripe\Climate\Product $product Unique ID for the Climate Product this order is purchasing. + * @property null|int $product_substituted_at Time at which the order's product was substituted for a different product. Measured in seconds since the Unix epoch. + * @property string $status The current status of this order. + */ +class Order extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'climate.order'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const CANCELLATION_REASON_EXPIRED = 'expired'; + const CANCELLATION_REASON_PRODUCT_UNAVAILABLE = 'product_unavailable'; + const CANCELLATION_REASON_REQUESTED = 'requested'; + + const STATUS_AWAITING_FUNDS = 'awaiting_funds'; + const STATUS_CANCELED = 'canceled'; + const STATUS_CONFIRMED = 'confirmed'; + const STATUS_DELIVERED = 'delivered'; + const STATUS_OPEN = 'open'; + + /** + * Creates a Climate order object for a given Climate product. The order will be + * processed immediately after creation and payment will be deducted your EDD\Vendor\Stripe + * balance. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Order the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Lists all Climate order objects. The orders are returned sorted by creation + * date, with the most recently created orders appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Climate\Order> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a Climate order object with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Order + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified order by setting the values of the parameters passed. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Order the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Order the canceled order + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Climate/Product.php b/libraries/Stripe/lib/Climate/Product.php new file mode 100644 index 00000000000..bc705e63a03 --- /dev/null +++ b/libraries/Stripe/lib/Climate/Product.php @@ -0,0 +1,60 @@ +climsku_. See carbon removal inventory for a list of available carbon removal products. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property \EDD\Vendor\Stripe\StripeObject $current_prices_per_metric_ton Current prices for a metric ton of carbon removal in a currency's smallest unit. + * @property null|int $delivery_year The year in which the carbon removal is expected to be delivered. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $metric_tons_available The quantity of metric tons available for reservation. + * @property string $name The Climate product's name. + * @property \EDD\Vendor\Stripe\Climate\Supplier[] $suppliers The carbon removal suppliers that fulfill orders for this Climate product. + */ +class Product extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'climate.product'; + + /** + * Lists all available Climate product objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Climate\Product> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a Climate product with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Product + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Climate/Supplier.php b/libraries/Stripe/lib/Climate/Supplier.php new file mode 100644 index 00000000000..5cc42964982 --- /dev/null +++ b/libraries/Stripe/lib/Climate/Supplier.php @@ -0,0 +1,61 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject[] $locations The locations in which this supplier operates. + * @property string $name Name of this carbon removal supplier. + * @property string $removal_pathway The scientific pathway used for carbon removal. + */ +class Supplier extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'climate.supplier'; + + const REMOVAL_PATHWAY_BIOMASS_CARBON_REMOVAL_AND_STORAGE = 'biomass_carbon_removal_and_storage'; + const REMOVAL_PATHWAY_DIRECT_AIR_CAPTURE = 'direct_air_capture'; + const REMOVAL_PATHWAY_ENHANCED_WEATHERING = 'enhanced_weathering'; + + /** + * Lists all available Climate supplier objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Climate\Supplier> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a Climate supplier object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Supplier + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Collection.php b/libraries/Stripe/lib/Collection.php new file mode 100644 index 00000000000..f46c899a1af --- /dev/null +++ b/libraries/Stripe/lib/Collection.php @@ -0,0 +1,321 @@ + + * + * @property string $object + * @property string $url + * @property bool $has_more + * @property TStripeObject[] $data + */ +class Collection extends StripeObject implements \Countable, \IteratorAggregate +{ + const OBJECT_NAME = 'list'; + + use ApiOperations\Request; + + /** @var array */ + protected $filters = []; + + /** + * @return string the base URL for the given class + */ + public static function baseUrl() + { + return Stripe::$apiBase; + } + + /** + * Returns the filters. + * + * @return array the filters + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Sets the filters, removing paging options. + * + * @param array $filters the filters + */ + public function setFilters($filters) + { + $this->filters = $filters; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($k) + { + if (\is_string($k)) { + return parent::offsetGet($k); + } + $msg = "You tried to access the {$k} index, but Collection " . + 'types only support string keys. (HINT: List calls ' . + 'return an object with a `data` (which is the data ' . + "array). You likely want to call ->data[{$k}])"; + + throw new Exception\InvalidArgumentException($msg); + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws Exception\ApiErrorException + * + * @return Collection + */ + public function all($params = null, $opts = null) + { + self::_validateParams($params); + list($url, $params) = $this->extractPathAndUpdateParams($params); + + list($response, $opts) = $this->_request('get', $url, $params, $opts); + $obj = Util\Util::convertToStripeObject($response, $opts); + if (!($obj instanceof \EDD\Vendor\Stripe\Collection)) { + throw new \EDD\Vendor\Stripe\Exception\UnexpectedValueException( + 'Expected type ' . \EDD\Vendor\Stripe\Collection::class . ', got "' . \get_class($obj) . '" instead.' + ); + } + $obj->setFilters($params); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws Exception\ApiErrorException + * + * @return TStripeObject + */ + public function create($params = null, $opts = null) + { + self::_validateParams($params); + list($url, $params) = $this->extractPathAndUpdateParams($params); + + list($response, $opts) = $this->_request('post', $url, $params, $opts); + + return Util\Util::convertToStripeObject($response, $opts); + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws Exception\ApiErrorException + * + * @return TStripeObject + */ + public function retrieve($id, $params = null, $opts = null) + { + self::_validateParams($params); + list($url, $params) = $this->extractPathAndUpdateParams($params); + + $id = Util\Util::utf8($id); + $extn = \urlencode($id); + list($response, $opts) = $this->_request( + 'get', + "{$url}/{$extn}", + $params, + $opts + ); + + return Util\Util::convertToStripeObject($response, $opts); + } + + /** + * @return int the number of objects in the current page + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->data); + } + + /** + * @return \ArrayIterator an iterator that can be used to iterate + * across objects in the current page + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + /** + * @return \ArrayIterator an iterator that can be used to iterate + * backwards across objects in the current page + */ + public function getReverseIterator() + { + return new \ArrayIterator(\array_reverse($this->data)); + } + + /** + * @throws Exception\ApiErrorException + * + * @return \Generator|TStripeObject[] A generator that can be used to + * iterate across all objects across all pages. As page boundaries are + * encountered, the next page will be fetched automatically for + * continued iteration. + */ + public function autoPagingIterator() + { + $page = $this; + + while (true) { + $filters = $this->filters ?: []; + if (\array_key_exists('ending_before', $filters) + && !\array_key_exists('starting_after', $filters)) { + foreach ($page->getReverseIterator() as $item) { + yield $item; + } + $page = $page->previousPage(); + } else { + foreach ($page as $item) { + yield $item; + } + $page = $page->nextPage(); + } + + if ($page->isEmpty()) { + break; + } + } + } + + /** + * Returns an empty collection. This is returned from {@see nextPage()} + * when we know that there isn't a next page in order to replicate the + * behavior of the API when it attempts to return a page beyond the last. + * + * @param null|array|string $opts + * + * @return Collection + */ + public static function emptyCollection($opts = null) + { + return Collection::constructFrom(['data' => []], $opts); + } + + /** + * Returns true if the page object contains no element. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->data); + } + + /** + * Fetches the next page in the resource list (if there is one). + * + * This method will try to respect the limit of the current page. If none + * was given, the default limit will be fetched again. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws Exception\ApiErrorException + * + * @return Collection + */ + public function nextPage($params = null, $opts = null) + { + if (!$this->has_more) { + return static::emptyCollection($opts); + } + + $lastId = \end($this->data)->id; + + $params = \array_merge( + $this->filters ?: [], + ['starting_after' => $lastId], + $params ?: [] + ); + + return $this->all($params, $opts); + } + + /** + * Fetches the previous page in the resource list (if there is one). + * + * This method will try to respect the limit of the current page. If none + * was given, the default limit will be fetched again. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws Exception\ApiErrorException + * + * @return Collection + */ + public function previousPage($params = null, $opts = null) + { + if (!$this->has_more) { + return static::emptyCollection($opts); + } + + $firstId = $this->data[0]->id; + + $params = \array_merge( + $this->filters ?: [], + ['ending_before' => $firstId], + $params ?: [] + ); + + return $this->all($params, $opts); + } + + /** + * Gets the first item from the current page. Returns `null` if the current page is empty. + * + * @return null|TStripeObject + */ + public function first() + { + return \count($this->data) > 0 ? $this->data[0] : null; + } + + /** + * Gets the last item from the current page. Returns `null` if the current page is empty. + * + * @return null|TStripeObject + */ + public function last() + { + return \count($this->data) > 0 ? $this->data[\count($this->data) - 1] : null; + } + + private function extractPathAndUpdateParams($params) + { + $url = \parse_url($this->url); + if (!isset($url['path'])) { + throw new Exception\UnexpectedValueException("Could not parse list url into parts: {$url}"); + } + + if (isset($url['query'])) { + // If the URL contains a query param, parse it out into $params so they + // don't interact weirdly with each other. + $query = []; + \parse_str($url['query'], $query); + $params = \array_merge($params ?: [], $query); + } + + return [$url['path'], $params]; + } +} diff --git a/libraries/Stripe/lib/ConfirmationToken.php b/libraries/Stripe/lib/ConfirmationToken.php new file mode 100644 index 00000000000..0351e440986 --- /dev/null +++ b/libraries/Stripe/lib/ConfirmationToken.php @@ -0,0 +1,56 @@ +Finalize payments on the server + * - Build two-step confirmation. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|int $expires_at Time at which this ConfirmationToken expires and can no longer be used to confirm a PaymentIntent or SetupIntent. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $mandate_data Data used for generating a Mandate. + * @property null|string $payment_intent ID of the PaymentIntent that this ConfirmationToken was used to confirm, or null if this ConfirmationToken has not yet been used. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_options Payment-method-specific configuration for this ConfirmationToken. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_preview Payment details collected by the Payment Element, used to create a PaymentMethod when a PaymentIntent or SetupIntent is confirmed with this ConfirmationToken. + * @property null|string $return_url Return URL used to confirm the Intent. + * @property null|string $setup_future_usage

    Indicates that you intend to make future payments with this ConfirmationToken's payment method.

    The presence of this property will attach the payment method to the PaymentIntent's Customer, if present, after the PaymentIntent is confirmed and any required actions from the user are complete.

    + * @property null|string $setup_intent ID of the SetupIntent that this ConfirmationToken was used to confirm, or null if this ConfirmationToken has not yet been used. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping Shipping information collected on this ConfirmationToken. + * @property bool $use_stripe_sdk Indicates whether the EDD\Vendor\Stripe SDK is used to handle confirmation flow. Defaults to true on ConfirmationToken. + */ +class ConfirmationToken extends ApiResource +{ + const OBJECT_NAME = 'confirmation_token'; + + const SETUP_FUTURE_USAGE_OFF_SESSION = 'off_session'; + const SETUP_FUTURE_USAGE_ON_SESSION = 'on_session'; + + /** + * Retrieves an existing ConfirmationToken object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ConfirmationToken + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/ConnectCollectionTransfer.php b/libraries/Stripe/lib/ConnectCollectionTransfer.php new file mode 100644 index 00000000000..b9e4e07942b --- /dev/null +++ b/libraries/Stripe/lib/ConnectCollectionTransfer.php @@ -0,0 +1,18 @@ +ISO currency code, in lowercase. Must be a supported currency. + * @property string|\EDD\Vendor\Stripe\Account $destination ID of the account that funds are being collected for. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + */ +class ConnectCollectionTransfer extends ApiResource +{ + const OBJECT_NAME = 'connect_collection_transfer'; +} diff --git a/libraries/Stripe/lib/CountrySpec.php b/libraries/Stripe/lib/CountrySpec.php new file mode 100644 index 00000000000..43d36eb081e --- /dev/null +++ b/libraries/Stripe/lib/CountrySpec.php @@ -0,0 +1,63 @@ +an online + * guide. + * + * @property string $id Unique identifier for the object. Represented as the ISO country code for this country. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string $default_currency The default currency for this country. This applies to both payment methods and bank accounts. + * @property \EDD\Vendor\Stripe\StripeObject $supported_bank_account_currencies Currencies that can be accepted in the specific country (for transfers). + * @property string[] $supported_payment_currencies Currencies that can be accepted in the specified country (for payments). + * @property string[] $supported_payment_methods Payment methods available in the specified country. You may need to enable some payment methods (e.g., ACH) on your account before they appear in this list. The stripe payment method refers to charging through your platform. + * @property string[] $supported_transfer_countries Countries that can accept transfers from the specified country. + * @property \EDD\Vendor\Stripe\StripeObject $verification_fields + */ +class CountrySpec extends ApiResource +{ + const OBJECT_NAME = 'country_spec'; + + /** + * Lists all Country Spec objects available in the API. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CountrySpec> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Returns a Country Spec for a given Country code. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CountrySpec + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Coupon.php b/libraries/Stripe/lib/Coupon.php new file mode 100644 index 00000000000..f7c35dc80ec --- /dev/null +++ b/libraries/Stripe/lib/Coupon.php @@ -0,0 +1,159 @@ +subscriptions, invoices, + * checkout sessions, quotes, and more. Coupons do not work with conventional one-off charges or payment intents. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|int $amount_off Amount (in the currency specified) that will be taken off the subtotal of any invoices for this customer. + * @property null|\EDD\Vendor\Stripe\StripeObject $applies_to + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $currency If amount_off has been set, the three-letter ISO code for the currency of the amount to take off. + * @property null|\EDD\Vendor\Stripe\StripeObject $currency_options Coupons defined in each available currency option. Each key must be a three-letter ISO currency code and a supported currency. + * @property string $duration One of forever, once, and repeating. Describes how long a customer who applies this coupon will get the discount. + * @property null|int $duration_in_months If duration is repeating, the number of months the coupon applies. Null if coupon duration is forever or once. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|int $max_redemptions Maximum number of times this coupon can be redeemed, in total, across all customers, before it is no longer valid. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $name Name of the coupon displayed to customers on for instance invoices or receipts. + * @property null|float $percent_off Percent that will be taken off the subtotal of any invoices for this customer for the duration of the coupon. For example, a coupon with percent_off of 50 will make a $ (or local equivalent)100 invoice $ (or local equivalent)50 instead. + * @property null|int $redeem_by Date after which the coupon can no longer be redeemed. + * @property int $times_redeemed Number of times this coupon has been applied to a customer. + * @property bool $valid Taking account of the above properties, whether this coupon can still be applied to a customer. + */ +class Coupon extends ApiResource +{ + const OBJECT_NAME = 'coupon'; + + use ApiOperations\Update; + + const DURATION_FOREVER = 'forever'; + const DURATION_ONCE = 'once'; + const DURATION_REPEATING = 'repeating'; + + /** + * You can create coupons easily via the coupon management page of the + * EDD\Vendor\Stripe dashboard. Coupon creation is also accessible via the API if you need to + * create coupons on the fly. + * + * A coupon has either a percent_off or an amount_off and + * currency. If you set an amount_off, that amount will + * be subtracted from any invoice’s subtotal. For example, an invoice with a + * subtotal of 100 will have a final total of + * 0 if a coupon with an amount_off of + * 200 is applied to it and an invoice with a subtotal of + * 300 will have a final total of 100 if + * a coupon with an amount_off of 200 is applied to + * it. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Coupon the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * You can delete coupons via the coupon management page of the + * EDD\Vendor\Stripe dashboard. However, deleting a coupon does not affect any customers who + * have already applied the coupon; it means that new customers can’t redeem the + * coupon. You can also delete coupons via the API. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Coupon the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of your coupons. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Coupon> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the coupon with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Coupon + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the metadata of a coupon. Other coupon details (currency, duration, + * amount_off) are, by design, not editable. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Coupon the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/CreditNote.php b/libraries/Stripe/lib/CreditNote.php new file mode 100644 index 00000000000..eff375b8756 --- /dev/null +++ b/libraries/Stripe/lib/CreditNote.php @@ -0,0 +1,229 @@ +Credit notes + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount The integer amount in cents (or local equivalent) representing the total amount of the credit note, including tax. + * @property int $amount_shipping This is the sum of all the shipping amounts. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string|\EDD\Vendor\Stripe\Customer $customer ID of the customer. + * @property null|string|\EDD\Vendor\Stripe\CustomerBalanceTransaction $customer_balance_transaction Customer balance transaction related to this credit note. + * @property int $discount_amount The integer amount in cents (or local equivalent) representing the total amount of discount that was credited. + * @property \EDD\Vendor\Stripe\StripeObject[] $discount_amounts The aggregate amounts calculated per discount for all line items. + * @property null|int $effective_at The date when this credit note is in effect. Same as created unless overwritten. When defined, this value replaces the system-generated 'Date of issue' printed on the credit note PDF. + * @property string|\EDD\Vendor\Stripe\Invoice $invoice ID of the invoice. + * @property \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CreditNoteLineItem> $lines Line items that make up the credit note + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $memo Customer-facing text that appears on the credit note PDF. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $number A unique number that identifies this particular credit note and appears on the PDF of the credit note and its associated invoice. + * @property null|int $out_of_band_amount Amount that was credited outside of Stripe. + * @property string $pdf The link to download the PDF of the credit note. + * @property null|string $reason Reason for issuing this credit note, one of duplicate, fraudulent, order_change, or product_unsatisfactory + * @property null|string|\EDD\Vendor\Stripe\Refund $refund Refund related to this credit note. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_cost The details of the cost of shipping, including the ShippingRate applied to the invoice. + * @property string $status Status of this credit note, one of issued or void. Learn more about voiding credit notes. + * @property int $subtotal The integer amount in cents (or local equivalent) representing the amount of the credit note, excluding exclusive tax and invoice level discounts. + * @property null|int $subtotal_excluding_tax The integer amount in cents (or local equivalent) representing the amount of the credit note, excluding all tax and invoice level discounts. + * @property \EDD\Vendor\Stripe\StripeObject[] $tax_amounts The aggregate amounts calculated per tax rate for all line items. + * @property int $total The integer amount in cents (or local equivalent) representing the total amount of the credit note, including tax and all discount. + * @property null|int $total_excluding_tax The integer amount in cents (or local equivalent) representing the total amount of the credit note, excluding tax, but including discounts. + * @property string $type Type of this credit note, one of pre_payment or post_payment. A pre_payment credit note means it was issued when the invoice was open. A post_payment credit note means it was issued when the invoice was paid. + * @property null|int $voided_at The time that the credit note was voided. + */ +class CreditNote extends ApiResource +{ + const OBJECT_NAME = 'credit_note'; + + use ApiOperations\NestedResource; + use ApiOperations\Update; + + const REASON_DUPLICATE = 'duplicate'; + const REASON_FRAUDULENT = 'fraudulent'; + const REASON_ORDER_CHANGE = 'order_change'; + const REASON_PRODUCT_UNSATISFACTORY = 'product_unsatisfactory'; + + const STATUS_ISSUED = 'issued'; + const STATUS_VOID = 'void'; + + const TYPE_POST_PAYMENT = 'post_payment'; + const TYPE_PRE_PAYMENT = 'pre_payment'; + + /** + * Issue a credit note to adjust the amount of a finalized invoice. For a + * status=open invoice, a credit note reduces its + * amount_due. For a status=paid invoice, a credit note + * does not affect its amount_due. Instead, it can result in any + * combination of the following:. + * + *
    • Refund: create a new refund (using refund_amount) or link + * an existing refund (using refund).
    • Customer balance + * credit: credit the customer’s balance (using credit_amount) which + * will be automatically applied to their next invoice when it’s finalized.
    • + *
    • Outside of EDD\Vendor\Stripe credit: record the amount that is or will be credited + * outside of EDD\Vendor\Stripe (using out_of_band_amount).
    + * + * For post-payment credit notes the sum of the refund, credit and outside of + * EDD\Vendor\Stripe amounts must equal the credit note total. + * + * You may issue multiple credit notes for an invoice. Each credit note will + * increment the invoice’s pre_payment_credit_notes_amount or + * post_payment_credit_notes_amount depending on its + * status at the time of credit note creation. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of credit notes. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CreditNote> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the credit note object with the given identifier. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates an existing credit note. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote the previewed credit note + */ + public static function preview($params = null, $opts = null) + { + $url = static::classUrl() . '/preview'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CreditNoteLineItem> list of credit note line items + */ + public static function previewLines($params = null, $opts = null) + { + $url = static::classUrl() . '/preview/lines'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote the voided credit note + */ + public function voidCreditNote($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/void'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + const PATH_LINES = '/lines'; + + /** + * @param string $id the ID of the credit note on which to retrieve the credit note line items + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CreditNoteLineItem> the list of credit note line items + */ + public static function allLines($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_LINES, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/CreditNoteLineItem.php b/libraries/Stripe/lib/CreditNoteLineItem.php new file mode 100644 index 00000000000..573eeec1dbd --- /dev/null +++ b/libraries/Stripe/lib/CreditNoteLineItem.php @@ -0,0 +1,30 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|int $quantity The number of units of product being credited. + * @property \EDD\Vendor\Stripe\StripeObject[] $tax_amounts The amount of tax calculated per tax rate for this line item + * @property \EDD\Vendor\Stripe\TaxRate[] $tax_rates The tax rates which apply to the line item. + * @property string $type The type of the credit note line item, one of invoice_line_item or custom_line_item. When the type is invoice_line_item there is an additional invoice_line_item property on the resource the value of which is the id of the credited line item on the invoice. + * @property null|int $unit_amount The cost of each unit of product being credited. + * @property null|string $unit_amount_decimal Same as unit_amount, but contains a decimal value with at most 12 decimal places. + * @property null|string $unit_amount_excluding_tax The amount in cents (or local equivalent) representing the unit amount being credited for this line item, excluding all tax and discounts. + */ +class CreditNoteLineItem extends ApiResource +{ + const OBJECT_NAME = 'credit_note_line_item'; +} diff --git a/libraries/Stripe/lib/Customer.php b/libraries/Stripe/lib/Customer.php new file mode 100644 index 00000000000..eacf82c7598 --- /dev/null +++ b/libraries/Stripe/lib/Customer.php @@ -0,0 +1,501 @@ +Save a card during payment + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $address The customer's address. + * @property null|int $balance The current balance, if any, that's stored on the customer. If negative, the customer has credit to apply to their next invoice. If positive, the customer has an amount owed that's added to their next invoice. The balance only considers amounts that EDD\Vendor\Stripe hasn't successfully applied to any invoice. It doesn't reflect unpaid invoices. This balance is only taken into account after invoices finalize. + * @property null|\EDD\Vendor\Stripe\CashBalance $cash_balance The current funds being held by EDD\Vendor\Stripe on behalf of the customer. You can apply these funds towards payment intents when the source is "cash_balance". The settings[reconciliation_mode] field describes if these funds apply to these payment intents manually or automatically. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $currency Three-letter ISO code for the currency the customer can be charged in for recurring billing purposes. + * @property null|string|\EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source $default_source

    ID of the default payment source for the customer.

    If you use payment methods created through the PaymentMethods API, see the invoice_settings.default_payment_method field instead.

    + * @property null|bool $delinquent

    Tracks the most recent state change on any invoice belonging to the customer. Paying an invoice or marking it uncollectible via the API will set this field to false. An automatic payment failure or passing the invoice.due_date will set this field to true.

    If an invoice becomes uncollectible by dunning, delinquent doesn't reset to false.

    If you care whether the customer has paid their most recent subscription invoice, use subscription.status instead. Paying or marking uncollectible any customer invoice regardless of whether it is the latest invoice for a subscription will always set this field to false.

    + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|\EDD\Vendor\Stripe\Discount $discount Describes the current discount active on the customer, if there is one. + * @property null|string $email The customer's email address. + * @property null|\EDD\Vendor\Stripe\StripeObject $invoice_credit_balance The current multi-currency balances, if any, that's stored on the customer. If positive in a currency, the customer has a credit to apply to their next invoice denominated in that currency. If negative, the customer has an amount owed that's added to their next invoice denominated in that currency. These balances don't apply to unpaid invoices. They solely track amounts that EDD\Vendor\Stripe hasn't successfully applied to any invoice. EDD\Vendor\Stripe only applies a balance in a specific currency to an invoice after that invoice (which is in the same currency) finalizes. + * @property null|string $invoice_prefix The prefix for the customer used to generate unique invoice numbers. + * @property null|\EDD\Vendor\Stripe\StripeObject $invoice_settings + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $name The customer's full name or business name. + * @property null|int $next_invoice_sequence The suffix of the customer's next invoice number (for example, 0001). When the account uses account level sequencing, this parameter is ignored in API requests and the field omitted in API responses. + * @property null|string $phone The customer's phone number. + * @property null|string[] $preferred_locales The customer's preferred locales (languages), ordered by preference. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping Mailing and shipping address for the customer. Appears on invoices emailed to this customer. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source> $sources The customer's payment sources, if any. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Subscription> $subscriptions The customer's current subscriptions, if any. + * @property null|\EDD\Vendor\Stripe\StripeObject $tax + * @property null|string $tax_exempt Describes the customer's tax exemption status, which is none, exempt, or reverse. When set to reverse, invoice and receipt PDFs include the following text: "Reverse charge". + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TaxId> $tax_ids The customer's tax IDs. + * @property null|string|\EDD\Vendor\Stripe\TestHelpers\TestClock $test_clock ID of the test clock that this customer belongs to. + */ +class Customer extends ApiResource +{ + const OBJECT_NAME = 'customer'; + + use ApiOperations\NestedResource; + use ApiOperations\Update; + + const TAX_EXEMPT_EXEMPT = 'exempt'; + const TAX_EXEMPT_NONE = 'none'; + const TAX_EXEMPT_REVERSE = 'reverse'; + + /** + * Creates a new customer object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Customer the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Permanently deletes a customer. It cannot be undone. Also immediately cancels + * any active subscriptions on the customer. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Customer the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of your customers. The customers are returned sorted by creation + * date, with the most recent customers appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Customer> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a Customer object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Customer + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified customer by setting the values of the parameters passed. + * Any parameters not provided will be left unchanged. For example, if you pass the + * source parameter, that becomes the customer’s active source + * (e.g., a card) to be used for all charges in the future. When you update a + * customer to a new valid card source by passing the source + * parameter: for each of the customer’s current subscriptions, if the subscription + * bills automatically and is in the past_due state, then the latest + * open invoice for the subscription with automatic collection enabled will be + * retried. This retry will not count as an automatic retry, and will not affect + * the next regularly scheduled payment for the invoice. Changing the + * default_source for a customer will not trigger this behavior. + * + * This request accepts mostly the same arguments as the customer creation call. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Customer the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + public static function getSavedNestedResources() + { + static $savedNestedResources = null; + if (null === $savedNestedResources) { + $savedNestedResources = new Util\Set([ + 'source', + ]); + } + + return $savedNestedResources; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @return \EDD\Vendor\Stripe\Customer the updated customer + */ + public function deleteDiscount($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/discount'; + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom(['discount' => null], $opts, true); + + return $this; + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PaymentMethod> list of payment methods + */ + public static function allPaymentMethods($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/payment_methods'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param string $payment_method + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod the retrieved payment method + */ + public function retrievePaymentMethod($payment_method, $params = null, $opts = null) + { + $url = $this->instanceUrl() . '/payment_methods/' . $payment_method; + list($response, $opts) = $this->_request('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Customer> the customer search results + */ + public static function search($params = null, $opts = null) + { + $url = '/v1/customers/search'; + + return static::_requestPage($url, \EDD\Vendor\Stripe\SearchResult::class, $params, $opts); + } + + const PATH_BALANCE_TRANSACTIONS = '/balance_transactions'; + + /** + * @param string $id the ID of the customer on which to retrieve the customer balance transactions + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CustomerBalanceTransaction> the list of customer balance transactions + */ + public static function allBalanceTransactions($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_BALANCE_TRANSACTIONS, $params, $opts); + } + + /** + * @param string $id the ID of the customer on which to create the customer balance transaction + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerBalanceTransaction + */ + public static function createBalanceTransaction($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_BALANCE_TRANSACTIONS, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the customer balance transaction belongs + * @param string $balanceTransactionId the ID of the customer balance transaction to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerBalanceTransaction + */ + public static function retrieveBalanceTransaction($id, $balanceTransactionId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_BALANCE_TRANSACTIONS, $balanceTransactionId, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the customer balance transaction belongs + * @param string $balanceTransactionId the ID of the customer balance transaction to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerBalanceTransaction + */ + public static function updateBalanceTransaction($id, $balanceTransactionId, $params = null, $opts = null) + { + return self::_updateNestedResource($id, static::PATH_BALANCE_TRANSACTIONS, $balanceTransactionId, $params, $opts); + } + const PATH_CASH_BALANCE_TRANSACTIONS = '/cash_balance_transactions'; + + /** + * @param string $id the ID of the customer on which to retrieve the customer cash balance transactions + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CustomerCashBalanceTransaction> the list of customer cash balance transactions + */ + public static function allCashBalanceTransactions($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_CASH_BALANCE_TRANSACTIONS, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the customer cash balance transaction belongs + * @param string $cashBalanceTransactionId the ID of the customer cash balance transaction to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerCashBalanceTransaction + */ + public static function retrieveCashBalanceTransaction($id, $cashBalanceTransactionId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_CASH_BALANCE_TRANSACTIONS, $cashBalanceTransactionId, $params, $opts); + } + const PATH_SOURCES = '/sources'; + + /** + * @param string $id the ID of the customer on which to retrieve the payment sources + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source> the list of payment sources (BankAccount, Card or Source) + */ + public static function allSources($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_SOURCES, $params, $opts); + } + + /** + * @param string $id the ID of the customer on which to create the payment source + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public static function createSource($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_SOURCES, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the payment source belongs + * @param string $sourceId the ID of the payment source to delete + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public static function deleteSource($id, $sourceId, $params = null, $opts = null) + { + return self::_deleteNestedResource($id, static::PATH_SOURCES, $sourceId, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the payment source belongs + * @param string $sourceId the ID of the payment source to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public static function retrieveSource($id, $sourceId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_SOURCES, $sourceId, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the payment source belongs + * @param string $sourceId the ID of the payment source to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public static function updateSource($id, $sourceId, $params = null, $opts = null) + { + return self::_updateNestedResource($id, static::PATH_SOURCES, $sourceId, $params, $opts); + } + const PATH_CASH_BALANCE = '/cash_balance'; + + /** + * @param string $id the ID of the customer to which the cash balance belongs + * @param null|array $params + * @param null|array|string $opts + * @param mixed $cashBalanceId + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CashBalance + */ + public static function retrieveCashBalance($id, $cashBalanceId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_CASH_BALANCE, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the cash balance belongs + * @param null|array $params + * @param null|array|string $opts + * @param mixed $cashBalanceId + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CashBalance + */ + public static function updateCashBalance($id, $cashBalanceId, $params = null, $opts = null) + { + return self::_updateNestedResource($id, static::PATH_CASH_BALANCE, $params, $opts); + } + const PATH_TAX_IDS = '/tax_ids'; + + /** + * @param string $id the ID of the customer on which to retrieve the tax ids + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TaxId> the list of tax ids + */ + public static function allTaxIds($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_TAX_IDS, $params, $opts); + } + + /** + * @param string $id the ID of the customer on which to create the tax id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public static function createTaxId($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_TAX_IDS, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the tax id belongs + * @param string $taxIdId the ID of the tax id to delete + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public static function deleteTaxId($id, $taxIdId, $params = null, $opts = null) + { + return self::_deleteNestedResource($id, static::PATH_TAX_IDS, $taxIdId, $params, $opts); + } + + /** + * @param string $id the ID of the customer to which the tax id belongs + * @param string $taxIdId the ID of the tax id to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public static function retrieveTaxId($id, $taxIdId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_TAX_IDS, $taxIdId, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/CustomerBalanceTransaction.php b/libraries/Stripe/lib/CustomerBalanceTransaction.php new file mode 100644 index 00000000000..8c2e250ef1b --- /dev/null +++ b/libraries/Stripe/lib/CustomerBalanceTransaction.php @@ -0,0 +1,99 @@ +Balance value, + * which denotes a debit or credit that's automatically applied to their next invoice upon finalization. + * You may modify the value directly by using the update customer API, + * or by creating a Customer Balance Transaction, which increments or decrements the customer's balance by the specified amount. + * + * Related guide: Customer balance + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount The amount of the transaction. A negative value is a credit for the customer's balance, and a positive value is a debit to the customer's balance. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string|\EDD\Vendor\Stripe\CreditNote $credit_note The ID of the credit note (if any) related to the transaction. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string|\EDD\Vendor\Stripe\Customer $customer The ID of the customer the transaction belongs to. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property int $ending_balance The customer's balance after the transaction was applied. A negative value decreases the amount due on the customer's next invoice. A positive value increases the amount due on the customer's next invoice. + * @property null|string|\EDD\Vendor\Stripe\Invoice $invoice The ID of the invoice (if any) related to the transaction. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $type Transaction type: adjustment, applied_to_invoice, credit_note, initial, invoice_overpaid, invoice_too_large, invoice_too_small, unspent_receiver_credit, or unapplied_from_invoice. See the Customer Balance page to learn more about transaction types. + */ +class CustomerBalanceTransaction extends ApiResource +{ + const OBJECT_NAME = 'customer_balance_transaction'; + + const TYPE_ADJUSTMENT = 'adjustment'; + const TYPE_APPLIED_TO_INVOICE = 'applied_to_invoice'; + const TYPE_CREDIT_NOTE = 'credit_note'; + const TYPE_INITIAL = 'initial'; + const TYPE_INVOICE_OVERPAID = 'invoice_overpaid'; + const TYPE_INVOICE_TOO_LARGE = 'invoice_too_large'; + const TYPE_INVOICE_TOO_SMALL = 'invoice_too_small'; + const TYPE_UNSPENT_RECEIVER_CREDIT = 'unspent_receiver_credit'; + + const TYPE_ADJUSTEMENT = 'adjustment'; + + /** + * @return string the API URL for this balance transaction + */ + public function instanceUrl() + { + $id = $this['id']; + $customer = $this['customer']; + if (!$id) { + throw new Exception\UnexpectedValueException( + "Could not determine which URL to request: class instance has invalid ID: {$id}", + null + ); + } + $id = Util\Util::utf8($id); + $customer = Util\Util::utf8($customer); + + $base = Customer::classUrl(); + $customerExtn = \urlencode($customer); + $extn = \urlencode($id); + + return "{$base}/{$customerExtn}/balance_transactions/{$extn}"; + } + + /** + * @param array|string $_id + * @param null|array|string $_opts + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function retrieve($_id, $_opts = null) + { + $msg = 'Customer Balance Transactions cannot be retrieved without a ' . + 'customer ID. Retrieve a Customer Balance Transaction using ' . + "`Customer::retrieveBalanceTransaction('customer_id', " . + "'balance_transaction_id')`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param string $_id + * @param null|array $_params + * @param null|array|string $_options + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function update($_id, $_params = null, $_options = null) + { + $msg = 'Customer Balance Transactions cannot be updated without a ' . + 'customer ID. Update a Customer Balance Transaction using ' . + "`Customer::updateBalanceTransaction('customer_id', " . + "'balance_transaction_id', \$updateParams)`."; + + throw new Exception\BadMethodCallException($msg); + } +} diff --git a/libraries/Stripe/lib/CustomerCashBalanceTransaction.php b/libraries/Stripe/lib/CustomerCashBalanceTransaction.php new file mode 100644 index 00000000000..387d53ab8c2 --- /dev/null +++ b/libraries/Stripe/lib/CustomerCashBalanceTransaction.php @@ -0,0 +1,42 @@ +ISO currency code, in lowercase. Must be a supported currency. + * @property string|\EDD\Vendor\Stripe\Customer $customer The customer whose available cash balance changed as a result of this transaction. + * @property int $ending_balance The total available cash balance for the specified currency after this transaction was applied. Represented in the smallest currency unit. + * @property null|\EDD\Vendor\Stripe\StripeObject $funded + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property int $net_amount The amount by which the cash balance changed, represented in the smallest currency unit. A positive value represents funds being added to the cash balance, a negative value represents funds being removed from the cash balance. + * @property null|\EDD\Vendor\Stripe\StripeObject $refunded_from_payment + * @property null|\EDD\Vendor\Stripe\StripeObject $transferred_to_balance + * @property string $type The type of the cash balance transaction. New types may be added in future. See Customer Balance to learn more about these types. + * @property null|\EDD\Vendor\Stripe\StripeObject $unapplied_from_payment + */ +class CustomerCashBalanceTransaction extends ApiResource +{ + const OBJECT_NAME = 'customer_cash_balance_transaction'; + + const TYPE_ADJUSTED_FOR_OVERDRAFT = 'adjusted_for_overdraft'; + const TYPE_APPLIED_TO_PAYMENT = 'applied_to_payment'; + const TYPE_FUNDED = 'funded'; + const TYPE_FUNDING_REVERSED = 'funding_reversed'; + const TYPE_REFUNDED_FROM_PAYMENT = 'refunded_from_payment'; + const TYPE_RETURN_CANCELED = 'return_canceled'; + const TYPE_RETURN_INITIATED = 'return_initiated'; + const TYPE_TRANSFERRED_TO_BALANCE = 'transferred_to_balance'; + const TYPE_UNAPPLIED_FROM_PAYMENT = 'unapplied_from_payment'; +} diff --git a/libraries/Stripe/lib/CustomerSession.php b/libraries/Stripe/lib/CustomerSession.php new file mode 100644 index 00000000000..5fd95ccafe3 --- /dev/null +++ b/libraries/Stripe/lib/CustomerSession.php @@ -0,0 +1,50 @@ +Customer Session with the Payment Element, + * Customer Session with the Pricing Table, + * Customer Session with the Buy Button. + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string $client_secret

    The client secret of this Customer Session. Used on the client to set up secure access to the given customer.

    The client secret can be used to provide access to customer from your frontend. It should not be stored, logged, or exposed to anyone other than the relevant customer. Make sure that you have TLS enabled on any page that includes the client secret.

    + * @property null|\EDD\Vendor\Stripe\StripeObject $components Configuration for the components supported by this Customer Session. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string|\EDD\Vendor\Stripe\Customer $customer The Customer the Customer Session was created for. + * @property int $expires_at The timestamp at which this Customer Session will expire. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + */ +class CustomerSession extends ApiResource +{ + const OBJECT_NAME = 'customer_session'; + + /** + * Creates a Customer Session object that includes a single-use client secret that + * you can use on your front-end to grant client-side API access for certain + * customer resources. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerSession the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Discount.php b/libraries/Stripe/lib/Discount.php new file mode 100644 index 00000000000..3bd88fd7b04 --- /dev/null +++ b/libraries/Stripe/lib/Discount.php @@ -0,0 +1,29 @@ +coupon or promotion code. + * It contains information about when the discount began, when it will end, and what it is applied to. + * + * Related guide: Applying discounts to subscriptions + * + * @property string $id The ID of the discount object. Discounts cannot be fetched by ID. Use expand[]=discounts in API calls to expand discount IDs in an array. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $checkout_session The Checkout session that this coupon is applied to, if it is applied to a particular session in payment mode. Will not be present for subscription mode. + * @property \EDD\Vendor\Stripe\Coupon $coupon A coupon contains information about a percent-off or amount-off discount you might want to apply to a customer. Coupons may be applied to subscriptions, invoices, checkout sessions, quotes, and more. Coupons do not work with conventional one-off charges or payment intents. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The ID of the customer associated with this discount. + * @property null|int $end If the coupon has a duration of repeating, the date that this discount will end. If the coupon has a duration of once or forever, this attribute will be null. + * @property null|string $invoice The invoice that the discount's coupon was applied to, if it was applied directly to a particular invoice. + * @property null|string $invoice_item The invoice item id (or invoice line item id for invoice line items of type='subscription') that the discount's coupon was applied to, if it was applied directly to a particular invoice item or invoice line item. + * @property null|string|\EDD\Vendor\Stripe\PromotionCode $promotion_code The promotion code applied to create this discount. + * @property int $start Date that the coupon was applied. + * @property null|string $subscription The subscription that this coupon is applied to, if it is applied to a particular subscription. + * @property null|string $subscription_item The subscription item that this coupon is applied to, if it is applied to a particular subscription item. + */ +class Discount extends ApiResource +{ + const OBJECT_NAME = 'discount'; +} diff --git a/libraries/Stripe/lib/Dispute.php b/libraries/Stripe/lib/Dispute.php new file mode 100644 index 00000000000..8b5dbf03e7e --- /dev/null +++ b/libraries/Stripe/lib/Dispute.php @@ -0,0 +1,144 @@ +Disputes and fraud + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Disputed amount. Usually the amount of the charge, but it can differ (usually because of currency fluctuation or because only part of the order is disputed). + * @property \EDD\Vendor\Stripe\BalanceTransaction[] $balance_transactions List of zero, one, or two balance transactions that show funds withdrawn and reinstated to your EDD\Vendor\Stripe account as a result of this dispute. + * @property string|\EDD\Vendor\Stripe\Charge $charge ID of the charge that's disputed. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property \EDD\Vendor\Stripe\StripeObject $evidence + * @property \EDD\Vendor\Stripe\StripeObject $evidence_details + * @property bool $is_charge_refundable If true, it's still possible to refund the disputed payment. After the payment has been fully refunded, no further funds are withdrawn from your EDD\Vendor\Stripe account as a result of this dispute. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $network_reason_code Network-dependent reason code for the dispute. + * @property null|string|\EDD\Vendor\Stripe\PaymentIntent $payment_intent ID of the PaymentIntent that's disputed. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_details + * @property string $reason Reason given by cardholder for dispute. Possible values are bank_cannot_process, check_returned, credit_not_processed, customer_initiated, debit_not_authorized, duplicate, fraudulent, general, incorrect_account_details, insufficient_funds, product_not_received, product_unacceptable, subscription_canceled, or unrecognized. Learn more about dispute reasons. + * @property string $status Current status of dispute. Possible values are warning_needs_response, warning_under_review, warning_closed, needs_response, under_review, won, or lost. + */ +class Dispute extends ApiResource +{ + const OBJECT_NAME = 'dispute'; + + use ApiOperations\Update; + + const REASON_BANK_CANNOT_PROCESS = 'bank_cannot_process'; + const REASON_CHECK_RETURNED = 'check_returned'; + const REASON_CREDIT_NOT_PROCESSED = 'credit_not_processed'; + const REASON_CUSTOMER_INITIATED = 'customer_initiated'; + const REASON_DEBIT_NOT_AUTHORIZED = 'debit_not_authorized'; + const REASON_DUPLICATE = 'duplicate'; + const REASON_FRAUDULENT = 'fraudulent'; + const REASON_GENERAL = 'general'; + const REASON_INCORRECT_ACCOUNT_DETAILS = 'incorrect_account_details'; + const REASON_INSUFFICIENT_FUNDS = 'insufficient_funds'; + const REASON_PRODUCT_NOT_RECEIVED = 'product_not_received'; + const REASON_PRODUCT_UNACCEPTABLE = 'product_unacceptable'; + const REASON_SUBSCRIPTION_CANCELED = 'subscription_canceled'; + const REASON_UNRECOGNIZED = 'unrecognized'; + + const STATUS_LOST = 'lost'; + const STATUS_NEEDS_RESPONSE = 'needs_response'; + const STATUS_UNDER_REVIEW = 'under_review'; + const STATUS_WARNING_CLOSED = 'warning_closed'; + const STATUS_WARNING_NEEDS_RESPONSE = 'warning_needs_response'; + const STATUS_WARNING_UNDER_REVIEW = 'warning_under_review'; + const STATUS_WON = 'won'; + + /** + * Returns a list of your disputes. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Dispute> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the dispute with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Dispute + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * When you get a dispute, contacting your customer is always the best first step. + * If that doesn’t work, you can submit evidence to help us resolve the dispute in + * your favor. You can do this in your dashboard, but if you prefer, + * you can use the API to submit evidence programmatically. + * + * Depending on your dispute type, different evidence fields will give you a better + * chance of winning your dispute. To figure out which evidence fields to provide, + * see our guide to dispute types. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Dispute the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Dispute the closed dispute + */ + public function close($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/close'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Entitlements/ActiveEntitlement.php b/libraries/Stripe/lib/Entitlements/ActiveEntitlement.php new file mode 100644 index 00000000000..2e05006d6db --- /dev/null +++ b/libraries/Stripe/lib/Entitlements/ActiveEntitlement.php @@ -0,0 +1,55 @@ +Feature that the customer is entitled to. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $lookup_key A unique key you provide as your own system identifier. This may be up to 80 characters. + */ +class ActiveEntitlement extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'entitlements.active_entitlement'; + + /** + * Retrieve a list of active entitlements for a customer. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Entitlements\ActiveEntitlement> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieve an active entitlement. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Entitlements\ActiveEntitlement + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Entitlements/ActiveEntitlementSummary.php b/libraries/Stripe/lib/Entitlements/ActiveEntitlementSummary.php new file mode 100644 index 00000000000..e18c218a589 --- /dev/null +++ b/libraries/Stripe/lib/Entitlements/ActiveEntitlementSummary.php @@ -0,0 +1,18 @@ + $entitlements The list of entitlements this customer has. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + */ +class ActiveEntitlementSummary extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'entitlements.active_entitlement_summary'; +} diff --git a/libraries/Stripe/lib/Entitlements/Feature.php b/libraries/Stripe/lib/Entitlements/Feature.php new file mode 100644 index 00000000000..317ac255bf1 --- /dev/null +++ b/libraries/Stripe/lib/Entitlements/Feature.php @@ -0,0 +1,105 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $lookup_key A unique key you provide as your own system identifier. This may be up to 80 characters. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $name The feature's name, for your own purpose, not meant to be displayable to the customer. + */ +class Feature extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'entitlements.feature'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + /** + * Creates a feature. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Entitlements\Feature the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Retrieve a list of features. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Entitlements\Feature> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a feature. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Entitlements\Feature + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Update a feature’s metadata or permanently deactivate it. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Entitlements\Feature the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/EphemeralKey.php b/libraries/Stripe/lib/EphemeralKey.php new file mode 100644 index 00000000000..62cfa5ccf59 --- /dev/null +++ b/libraries/Stripe/lib/EphemeralKey.php @@ -0,0 +1,61 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $secret The key's secret. You can use this value to make authorized requests to the EDD\Vendor\Stripe API. + */ +class EphemeralKey extends ApiResource +{ + const OBJECT_NAME = 'ephemeral_key'; + + /** + * Invalidates a short-lived API key for a given resource. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\EphemeralKey the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + use ApiOperations\Create { + create as protected _create; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\InvalidArgumentException if stripe_version is missing + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\EphemeralKey the created key + */ + public static function create($params = null, $opts = null) + { + if (!$opts || !isset($opts['stripe_version'])) { + throw new Exception\InvalidArgumentException('stripe_version must be specified to create an ephemeral key'); + } + + return self::_create($params, $opts); + } +} diff --git a/libraries/Stripe/lib/ErrorObject.php b/libraries/Stripe/lib/ErrorObject.php new file mode 100644 index 00000000000..30bf4bdf181 --- /dev/null +++ b/libraries/Stripe/lib/ErrorObject.php @@ -0,0 +1,247 @@ + null, + 'code' => null, + 'decline_code' => null, + 'doc_url' => null, + 'message' => null, + 'param' => null, + 'payment_intent' => null, + 'payment_method' => null, + 'setup_intent' => null, + 'source' => null, + 'type' => null, + ], $values); + parent::refreshFrom($values, $opts, $partial); + } +} diff --git a/libraries/Stripe/lib/Event.php b/libraries/Stripe/lib/Event.php new file mode 100644 index 00000000000..fd8e46e465d --- /dev/null +++ b/libraries/Stripe/lib/Event.php @@ -0,0 +1,573 @@ +Event + * object. For example, when a charge succeeds, we create a charge.succeeded + * event, and when an invoice payment attempt fails, we create an + * invoice.payment_failed event. Certain API requests might create multiple + * events. For example, if you create a new subscription for a + * customer, you receive both a customer.subscription.created event and a + * charge.succeeded event. + * + * Events occur when the state of another API resource changes. The event's data + * field embeds the resource's state at the time of the change. For + * example, a charge.succeeded event contains a charge, and an + * invoice.payment_failed event contains an invoice. + * + * As with other API resources, you can use endpoints to retrieve an + * individual event or a list of events + * from the API. We also have a separate + * webhooks system for sending the + * Event objects directly to an endpoint on your server. You can manage + * webhooks in your + * account settings. Learn how + * to listen for events + * so that your integration can automatically trigger reactions. + * + * When using Connect, you can also receive event notifications + * that occur in connected accounts. For these events, there's an + * additional account attribute in the received Event object. + * + * We only guarantee access to events through the Retrieve Event API + * for 30 days. + * + * This class includes constants for the possible string representations of + * event types. See https://stripe.com/docs/api#event_types for more details. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $account The connected account that originates the event. + * @property null|string $api_version The EDD\Vendor\Stripe API version used to render data. This property is populated only for events on or after October 31, 2014. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property \EDD\Vendor\Stripe\StripeObject $data + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property int $pending_webhooks Number of webhooks that haven't been successfully delivered (for example, to return a 20x response) to the URLs you specify. + * @property null|\EDD\Vendor\Stripe\StripeObject $request Information on the API request that triggers the event. + * @property string $type Description of the event (for example, invoice.created or charge.refunded). + */ +class Event extends ApiResource +{ + const OBJECT_NAME = 'event'; + + const ACCOUNT_APPLICATION_AUTHORIZED = 'account.application.authorized'; + const ACCOUNT_APPLICATION_DEAUTHORIZED = 'account.application.deauthorized'; + const ACCOUNT_EXTERNAL_ACCOUNT_CREATED = 'account.external_account.created'; + const ACCOUNT_EXTERNAL_ACCOUNT_DELETED = 'account.external_account.deleted'; + const ACCOUNT_EXTERNAL_ACCOUNT_UPDATED = 'account.external_account.updated'; + const ACCOUNT_UPDATED = 'account.updated'; + const APPLICATION_FEE_CREATED = 'application_fee.created'; + const APPLICATION_FEE_REFUNDED = 'application_fee.refunded'; + const APPLICATION_FEE_REFUND_UPDATED = 'application_fee.refund.updated'; + const BALANCE_AVAILABLE = 'balance.available'; + const BILLING_ALERT_TRIGGERED = 'billing.alert.triggered'; + const BILLING_PORTAL_CONFIGURATION_CREATED = 'billing_portal.configuration.created'; + const BILLING_PORTAL_CONFIGURATION_UPDATED = 'billing_portal.configuration.updated'; + const BILLING_PORTAL_SESSION_CREATED = 'billing_portal.session.created'; + const CAPABILITY_UPDATED = 'capability.updated'; + const CASH_BALANCE_FUNDS_AVAILABLE = 'cash_balance.funds_available'; + const CHARGE_CAPTURED = 'charge.captured'; + const CHARGE_DISPUTE_CLOSED = 'charge.dispute.closed'; + const CHARGE_DISPUTE_CREATED = 'charge.dispute.created'; + const CHARGE_DISPUTE_FUNDS_REINSTATED = 'charge.dispute.funds_reinstated'; + const CHARGE_DISPUTE_FUNDS_WITHDRAWN = 'charge.dispute.funds_withdrawn'; + const CHARGE_DISPUTE_UPDATED = 'charge.dispute.updated'; + const CHARGE_EXPIRED = 'charge.expired'; + const CHARGE_FAILED = 'charge.failed'; + const CHARGE_PENDING = 'charge.pending'; + const CHARGE_REFUNDED = 'charge.refunded'; + const CHARGE_REFUND_UPDATED = 'charge.refund.updated'; + const CHARGE_SUCCEEDED = 'charge.succeeded'; + const CHARGE_UPDATED = 'charge.updated'; + const CHECKOUT_SESSION_ASYNC_PAYMENT_FAILED = 'checkout.session.async_payment_failed'; + const CHECKOUT_SESSION_ASYNC_PAYMENT_SUCCEEDED = 'checkout.session.async_payment_succeeded'; + const CHECKOUT_SESSION_COMPLETED = 'checkout.session.completed'; + const CHECKOUT_SESSION_EXPIRED = 'checkout.session.expired'; + const CLIMATE_ORDER_CANCELED = 'climate.order.canceled'; + const CLIMATE_ORDER_CREATED = 'climate.order.created'; + const CLIMATE_ORDER_DELAYED = 'climate.order.delayed'; + const CLIMATE_ORDER_DELIVERED = 'climate.order.delivered'; + const CLIMATE_ORDER_PRODUCT_SUBSTITUTED = 'climate.order.product_substituted'; + const CLIMATE_PRODUCT_CREATED = 'climate.product.created'; + const CLIMATE_PRODUCT_PRICING_UPDATED = 'climate.product.pricing_updated'; + const COUPON_CREATED = 'coupon.created'; + const COUPON_DELETED = 'coupon.deleted'; + const COUPON_UPDATED = 'coupon.updated'; + const CREDIT_NOTE_CREATED = 'credit_note.created'; + const CREDIT_NOTE_UPDATED = 'credit_note.updated'; + const CREDIT_NOTE_VOIDED = 'credit_note.voided'; + const CUSTOMER_CASH_BALANCE_TRANSACTION_CREATED = 'customer_cash_balance_transaction.created'; + const CUSTOMER_CREATED = 'customer.created'; + const CUSTOMER_DELETED = 'customer.deleted'; + const CUSTOMER_DISCOUNT_CREATED = 'customer.discount.created'; + const CUSTOMER_DISCOUNT_DELETED = 'customer.discount.deleted'; + const CUSTOMER_DISCOUNT_UPDATED = 'customer.discount.updated'; + const CUSTOMER_SOURCE_CREATED = 'customer.source.created'; + const CUSTOMER_SOURCE_DELETED = 'customer.source.deleted'; + const CUSTOMER_SOURCE_EXPIRING = 'customer.source.expiring'; + const CUSTOMER_SOURCE_UPDATED = 'customer.source.updated'; + const CUSTOMER_SUBSCRIPTION_CREATED = 'customer.subscription.created'; + const CUSTOMER_SUBSCRIPTION_DELETED = 'customer.subscription.deleted'; + const CUSTOMER_SUBSCRIPTION_PAUSED = 'customer.subscription.paused'; + const CUSTOMER_SUBSCRIPTION_PENDING_UPDATE_APPLIED = 'customer.subscription.pending_update_applied'; + const CUSTOMER_SUBSCRIPTION_PENDING_UPDATE_EXPIRED = 'customer.subscription.pending_update_expired'; + const CUSTOMER_SUBSCRIPTION_RESUMED = 'customer.subscription.resumed'; + const CUSTOMER_SUBSCRIPTION_TRIAL_WILL_END = 'customer.subscription.trial_will_end'; + const CUSTOMER_SUBSCRIPTION_UPDATED = 'customer.subscription.updated'; + const CUSTOMER_TAX_ID_CREATED = 'customer.tax_id.created'; + const CUSTOMER_TAX_ID_DELETED = 'customer.tax_id.deleted'; + const CUSTOMER_TAX_ID_UPDATED = 'customer.tax_id.updated'; + const CUSTOMER_UPDATED = 'customer.updated'; + const ENTITLEMENTS_ACTIVE_ENTITLEMENT_SUMMARY_UPDATED = 'entitlements.active_entitlement_summary.updated'; + const FILE_CREATED = 'file.created'; + const FINANCIAL_CONNECTIONS_ACCOUNT_CREATED = 'financial_connections.account.created'; + const FINANCIAL_CONNECTIONS_ACCOUNT_DEACTIVATED = 'financial_connections.account.deactivated'; + const FINANCIAL_CONNECTIONS_ACCOUNT_DISCONNECTED = 'financial_connections.account.disconnected'; + const FINANCIAL_CONNECTIONS_ACCOUNT_REACTIVATED = 'financial_connections.account.reactivated'; + const FINANCIAL_CONNECTIONS_ACCOUNT_REFRESHED_BALANCE = 'financial_connections.account.refreshed_balance'; + const FINANCIAL_CONNECTIONS_ACCOUNT_REFRESHED_OWNERSHIP = 'financial_connections.account.refreshed_ownership'; + const FINANCIAL_CONNECTIONS_ACCOUNT_REFRESHED_TRANSACTIONS = 'financial_connections.account.refreshed_transactions'; + const IDENTITY_VERIFICATION_SESSION_CANCELED = 'identity.verification_session.canceled'; + const IDENTITY_VERIFICATION_SESSION_CREATED = 'identity.verification_session.created'; + const IDENTITY_VERIFICATION_SESSION_PROCESSING = 'identity.verification_session.processing'; + const IDENTITY_VERIFICATION_SESSION_REDACTED = 'identity.verification_session.redacted'; + const IDENTITY_VERIFICATION_SESSION_REQUIRES_INPUT = 'identity.verification_session.requires_input'; + const IDENTITY_VERIFICATION_SESSION_VERIFIED = 'identity.verification_session.verified'; + const INVOICEITEM_CREATED = 'invoiceitem.created'; + const INVOICEITEM_DELETED = 'invoiceitem.deleted'; + const INVOICE_CREATED = 'invoice.created'; + const INVOICE_DELETED = 'invoice.deleted'; + const INVOICE_FINALIZATION_FAILED = 'invoice.finalization_failed'; + const INVOICE_FINALIZED = 'invoice.finalized'; + const INVOICE_MARKED_UNCOLLECTIBLE = 'invoice.marked_uncollectible'; + const INVOICE_OVERDUE = 'invoice.overdue'; + const INVOICE_PAID = 'invoice.paid'; + const INVOICE_PAYMENT_ACTION_REQUIRED = 'invoice.payment_action_required'; + const INVOICE_PAYMENT_FAILED = 'invoice.payment_failed'; + const INVOICE_PAYMENT_SUCCEEDED = 'invoice.payment_succeeded'; + const INVOICE_SENT = 'invoice.sent'; + const INVOICE_UPCOMING = 'invoice.upcoming'; + const INVOICE_UPDATED = 'invoice.updated'; + const INVOICE_VOIDED = 'invoice.voided'; + const INVOICE_WILL_BE_DUE = 'invoice.will_be_due'; + const ISSUING_AUTHORIZATION_CREATED = 'issuing_authorization.created'; + const ISSUING_AUTHORIZATION_REQUEST = 'issuing_authorization.request'; + const ISSUING_AUTHORIZATION_UPDATED = 'issuing_authorization.updated'; + const ISSUING_CARDHOLDER_CREATED = 'issuing_cardholder.created'; + const ISSUING_CARDHOLDER_UPDATED = 'issuing_cardholder.updated'; + const ISSUING_CARD_CREATED = 'issuing_card.created'; + const ISSUING_CARD_UPDATED = 'issuing_card.updated'; + const ISSUING_DISPUTE_CLOSED = 'issuing_dispute.closed'; + const ISSUING_DISPUTE_CREATED = 'issuing_dispute.created'; + const ISSUING_DISPUTE_FUNDS_REINSTATED = 'issuing_dispute.funds_reinstated'; + const ISSUING_DISPUTE_FUNDS_RESCINDED = 'issuing_dispute.funds_rescinded'; + const ISSUING_DISPUTE_SUBMITTED = 'issuing_dispute.submitted'; + const ISSUING_DISPUTE_UPDATED = 'issuing_dispute.updated'; + const ISSUING_PERSONALIZATION_DESIGN_ACTIVATED = 'issuing_personalization_design.activated'; + const ISSUING_PERSONALIZATION_DESIGN_DEACTIVATED = 'issuing_personalization_design.deactivated'; + const ISSUING_PERSONALIZATION_DESIGN_REJECTED = 'issuing_personalization_design.rejected'; + const ISSUING_PERSONALIZATION_DESIGN_UPDATED = 'issuing_personalization_design.updated'; + const ISSUING_TOKEN_CREATED = 'issuing_token.created'; + const ISSUING_TOKEN_UPDATED = 'issuing_token.updated'; + const ISSUING_TRANSACTION_CREATED = 'issuing_transaction.created'; + const ISSUING_TRANSACTION_UPDATED = 'issuing_transaction.updated'; + const MANDATE_UPDATED = 'mandate.updated'; + const PAYMENT_INTENT_AMOUNT_CAPTURABLE_UPDATED = 'payment_intent.amount_capturable_updated'; + const PAYMENT_INTENT_CANCELED = 'payment_intent.canceled'; + const PAYMENT_INTENT_CREATED = 'payment_intent.created'; + const PAYMENT_INTENT_PARTIALLY_FUNDED = 'payment_intent.partially_funded'; + const PAYMENT_INTENT_PAYMENT_FAILED = 'payment_intent.payment_failed'; + const PAYMENT_INTENT_PROCESSING = 'payment_intent.processing'; + const PAYMENT_INTENT_REQUIRES_ACTION = 'payment_intent.requires_action'; + const PAYMENT_INTENT_SUCCEEDED = 'payment_intent.succeeded'; + const PAYMENT_LINK_CREATED = 'payment_link.created'; + const PAYMENT_LINK_UPDATED = 'payment_link.updated'; + const PAYMENT_METHOD_ATTACHED = 'payment_method.attached'; + const PAYMENT_METHOD_AUTOMATICALLY_UPDATED = 'payment_method.automatically_updated'; + const PAYMENT_METHOD_DETACHED = 'payment_method.detached'; + const PAYMENT_METHOD_UPDATED = 'payment_method.updated'; + const PAYOUT_CANCELED = 'payout.canceled'; + const PAYOUT_CREATED = 'payout.created'; + const PAYOUT_FAILED = 'payout.failed'; + const PAYOUT_PAID = 'payout.paid'; + const PAYOUT_RECONCILIATION_COMPLETED = 'payout.reconciliation_completed'; + const PAYOUT_UPDATED = 'payout.updated'; + const PERSON_CREATED = 'person.created'; + const PERSON_DELETED = 'person.deleted'; + const PERSON_UPDATED = 'person.updated'; + const PLAN_CREATED = 'plan.created'; + const PLAN_DELETED = 'plan.deleted'; + const PLAN_UPDATED = 'plan.updated'; + const PRICE_CREATED = 'price.created'; + const PRICE_DELETED = 'price.deleted'; + const PRICE_UPDATED = 'price.updated'; + const PRODUCT_CREATED = 'product.created'; + const PRODUCT_DELETED = 'product.deleted'; + const PRODUCT_UPDATED = 'product.updated'; + const PROMOTION_CODE_CREATED = 'promotion_code.created'; + const PROMOTION_CODE_UPDATED = 'promotion_code.updated'; + const QUOTE_ACCEPTED = 'quote.accepted'; + const QUOTE_CANCELED = 'quote.canceled'; + const QUOTE_CREATED = 'quote.created'; + const QUOTE_FINALIZED = 'quote.finalized'; + const RADAR_EARLY_FRAUD_WARNING_CREATED = 'radar.early_fraud_warning.created'; + const RADAR_EARLY_FRAUD_WARNING_UPDATED = 'radar.early_fraud_warning.updated'; + const REFUND_CREATED = 'refund.created'; + const REFUND_UPDATED = 'refund.updated'; + const REPORTING_REPORT_RUN_FAILED = 'reporting.report_run.failed'; + const REPORTING_REPORT_RUN_SUCCEEDED = 'reporting.report_run.succeeded'; + const REPORTING_REPORT_TYPE_UPDATED = 'reporting.report_type.updated'; + const REVIEW_CLOSED = 'review.closed'; + const REVIEW_OPENED = 'review.opened'; + const SETUP_INTENT_CANCELED = 'setup_intent.canceled'; + const SETUP_INTENT_CREATED = 'setup_intent.created'; + const SETUP_INTENT_REQUIRES_ACTION = 'setup_intent.requires_action'; + const SETUP_INTENT_SETUP_FAILED = 'setup_intent.setup_failed'; + const SETUP_INTENT_SUCCEEDED = 'setup_intent.succeeded'; + const SIGMA_SCHEDULED_QUERY_RUN_CREATED = 'sigma.scheduled_query_run.created'; + const SOURCE_CANCELED = 'source.canceled'; + const SOURCE_CHARGEABLE = 'source.chargeable'; + const SOURCE_FAILED = 'source.failed'; + const SOURCE_MANDATE_NOTIFICATION = 'source.mandate_notification'; + const SOURCE_REFUND_ATTRIBUTES_REQUIRED = 'source.refund_attributes_required'; + const SOURCE_TRANSACTION_CREATED = 'source.transaction.created'; + const SOURCE_TRANSACTION_UPDATED = 'source.transaction.updated'; + const SUBSCRIPTION_SCHEDULE_ABORTED = 'subscription_schedule.aborted'; + const SUBSCRIPTION_SCHEDULE_CANCELED = 'subscription_schedule.canceled'; + const SUBSCRIPTION_SCHEDULE_COMPLETED = 'subscription_schedule.completed'; + const SUBSCRIPTION_SCHEDULE_CREATED = 'subscription_schedule.created'; + const SUBSCRIPTION_SCHEDULE_EXPIRING = 'subscription_schedule.expiring'; + const SUBSCRIPTION_SCHEDULE_RELEASED = 'subscription_schedule.released'; + const SUBSCRIPTION_SCHEDULE_UPDATED = 'subscription_schedule.updated'; + const TAX_RATE_CREATED = 'tax_rate.created'; + const TAX_RATE_UPDATED = 'tax_rate.updated'; + const TAX_SETTINGS_UPDATED = 'tax.settings.updated'; + const TERMINAL_READER_ACTION_FAILED = 'terminal.reader.action_failed'; + const TERMINAL_READER_ACTION_SUCCEEDED = 'terminal.reader.action_succeeded'; + const TEST_HELPERS_TEST_CLOCK_ADVANCING = 'test_helpers.test_clock.advancing'; + const TEST_HELPERS_TEST_CLOCK_CREATED = 'test_helpers.test_clock.created'; + const TEST_HELPERS_TEST_CLOCK_DELETED = 'test_helpers.test_clock.deleted'; + const TEST_HELPERS_TEST_CLOCK_INTERNAL_FAILURE = 'test_helpers.test_clock.internal_failure'; + const TEST_HELPERS_TEST_CLOCK_READY = 'test_helpers.test_clock.ready'; + const TOPUP_CANCELED = 'topup.canceled'; + const TOPUP_CREATED = 'topup.created'; + const TOPUP_FAILED = 'topup.failed'; + const TOPUP_REVERSED = 'topup.reversed'; + const TOPUP_SUCCEEDED = 'topup.succeeded'; + const TRANSFER_CREATED = 'transfer.created'; + const TRANSFER_REVERSED = 'transfer.reversed'; + const TRANSFER_UPDATED = 'transfer.updated'; + const TREASURY_CREDIT_REVERSAL_CREATED = 'treasury.credit_reversal.created'; + const TREASURY_CREDIT_REVERSAL_POSTED = 'treasury.credit_reversal.posted'; + const TREASURY_DEBIT_REVERSAL_COMPLETED = 'treasury.debit_reversal.completed'; + const TREASURY_DEBIT_REVERSAL_CREATED = 'treasury.debit_reversal.created'; + const TREASURY_DEBIT_REVERSAL_INITIAL_CREDIT_GRANTED = 'treasury.debit_reversal.initial_credit_granted'; + const TREASURY_FINANCIAL_ACCOUNT_CLOSED = 'treasury.financial_account.closed'; + const TREASURY_FINANCIAL_ACCOUNT_CREATED = 'treasury.financial_account.created'; + const TREASURY_FINANCIAL_ACCOUNT_FEATURES_STATUS_UPDATED = 'treasury.financial_account.features_status_updated'; + const TREASURY_INBOUND_TRANSFER_CANCELED = 'treasury.inbound_transfer.canceled'; + const TREASURY_INBOUND_TRANSFER_CREATED = 'treasury.inbound_transfer.created'; + const TREASURY_INBOUND_TRANSFER_FAILED = 'treasury.inbound_transfer.failed'; + const TREASURY_INBOUND_TRANSFER_SUCCEEDED = 'treasury.inbound_transfer.succeeded'; + const TREASURY_OUTBOUND_PAYMENT_CANCELED = 'treasury.outbound_payment.canceled'; + const TREASURY_OUTBOUND_PAYMENT_CREATED = 'treasury.outbound_payment.created'; + const TREASURY_OUTBOUND_PAYMENT_EXPECTED_ARRIVAL_DATE_UPDATED = 'treasury.outbound_payment.expected_arrival_date_updated'; + const TREASURY_OUTBOUND_PAYMENT_FAILED = 'treasury.outbound_payment.failed'; + const TREASURY_OUTBOUND_PAYMENT_POSTED = 'treasury.outbound_payment.posted'; + const TREASURY_OUTBOUND_PAYMENT_RETURNED = 'treasury.outbound_payment.returned'; + const TREASURY_OUTBOUND_PAYMENT_TRACKING_DETAILS_UPDATED = 'treasury.outbound_payment.tracking_details_updated'; + const TREASURY_OUTBOUND_TRANSFER_CANCELED = 'treasury.outbound_transfer.canceled'; + const TREASURY_OUTBOUND_TRANSFER_CREATED = 'treasury.outbound_transfer.created'; + const TREASURY_OUTBOUND_TRANSFER_EXPECTED_ARRIVAL_DATE_UPDATED = 'treasury.outbound_transfer.expected_arrival_date_updated'; + const TREASURY_OUTBOUND_TRANSFER_FAILED = 'treasury.outbound_transfer.failed'; + const TREASURY_OUTBOUND_TRANSFER_POSTED = 'treasury.outbound_transfer.posted'; + const TREASURY_OUTBOUND_TRANSFER_RETURNED = 'treasury.outbound_transfer.returned'; + const TREASURY_OUTBOUND_TRANSFER_TRACKING_DETAILS_UPDATED = 'treasury.outbound_transfer.tracking_details_updated'; + const TREASURY_RECEIVED_CREDIT_CREATED = 'treasury.received_credit.created'; + const TREASURY_RECEIVED_CREDIT_FAILED = 'treasury.received_credit.failed'; + const TREASURY_RECEIVED_CREDIT_SUCCEEDED = 'treasury.received_credit.succeeded'; + const TREASURY_RECEIVED_DEBIT_CREATED = 'treasury.received_debit.created'; + + const TYPE_ACCOUNT_APPLICATION_AUTHORIZED = 'account.application.authorized'; + const TYPE_ACCOUNT_APPLICATION_DEAUTHORIZED = 'account.application.deauthorized'; + const TYPE_ACCOUNT_EXTERNAL_ACCOUNT_CREATED = 'account.external_account.created'; + const TYPE_ACCOUNT_EXTERNAL_ACCOUNT_DELETED = 'account.external_account.deleted'; + const TYPE_ACCOUNT_EXTERNAL_ACCOUNT_UPDATED = 'account.external_account.updated'; + const TYPE_ACCOUNT_UPDATED = 'account.updated'; + const TYPE_APPLICATION_FEE_CREATED = 'application_fee.created'; + const TYPE_APPLICATION_FEE_REFUNDED = 'application_fee.refunded'; + const TYPE_APPLICATION_FEE_REFUND_UPDATED = 'application_fee.refund.updated'; + const TYPE_BALANCE_AVAILABLE = 'balance.available'; + const TYPE_BILLING_ALERT_TRIGGERED = 'billing.alert.triggered'; + const TYPE_BILLING_PORTAL_CONFIGURATION_CREATED = 'billing_portal.configuration.created'; + const TYPE_BILLING_PORTAL_CONFIGURATION_UPDATED = 'billing_portal.configuration.updated'; + const TYPE_BILLING_PORTAL_SESSION_CREATED = 'billing_portal.session.created'; + const TYPE_CAPABILITY_UPDATED = 'capability.updated'; + const TYPE_CASH_BALANCE_FUNDS_AVAILABLE = 'cash_balance.funds_available'; + const TYPE_CHARGE_CAPTURED = 'charge.captured'; + const TYPE_CHARGE_DISPUTE_CLOSED = 'charge.dispute.closed'; + const TYPE_CHARGE_DISPUTE_CREATED = 'charge.dispute.created'; + const TYPE_CHARGE_DISPUTE_FUNDS_REINSTATED = 'charge.dispute.funds_reinstated'; + const TYPE_CHARGE_DISPUTE_FUNDS_WITHDRAWN = 'charge.dispute.funds_withdrawn'; + const TYPE_CHARGE_DISPUTE_UPDATED = 'charge.dispute.updated'; + const TYPE_CHARGE_EXPIRED = 'charge.expired'; + const TYPE_CHARGE_FAILED = 'charge.failed'; + const TYPE_CHARGE_PENDING = 'charge.pending'; + const TYPE_CHARGE_REFUNDED = 'charge.refunded'; + const TYPE_CHARGE_REFUND_UPDATED = 'charge.refund.updated'; + const TYPE_CHARGE_SUCCEEDED = 'charge.succeeded'; + const TYPE_CHARGE_UPDATED = 'charge.updated'; + const TYPE_CHECKOUT_SESSION_ASYNC_PAYMENT_FAILED = 'checkout.session.async_payment_failed'; + const TYPE_CHECKOUT_SESSION_ASYNC_PAYMENT_SUCCEEDED = 'checkout.session.async_payment_succeeded'; + const TYPE_CHECKOUT_SESSION_COMPLETED = 'checkout.session.completed'; + const TYPE_CHECKOUT_SESSION_EXPIRED = 'checkout.session.expired'; + const TYPE_CLIMATE_ORDER_CANCELED = 'climate.order.canceled'; + const TYPE_CLIMATE_ORDER_CREATED = 'climate.order.created'; + const TYPE_CLIMATE_ORDER_DELAYED = 'climate.order.delayed'; + const TYPE_CLIMATE_ORDER_DELIVERED = 'climate.order.delivered'; + const TYPE_CLIMATE_ORDER_PRODUCT_SUBSTITUTED = 'climate.order.product_substituted'; + const TYPE_CLIMATE_PRODUCT_CREATED = 'climate.product.created'; + const TYPE_CLIMATE_PRODUCT_PRICING_UPDATED = 'climate.product.pricing_updated'; + const TYPE_COUPON_CREATED = 'coupon.created'; + const TYPE_COUPON_DELETED = 'coupon.deleted'; + const TYPE_COUPON_UPDATED = 'coupon.updated'; + const TYPE_CREDIT_NOTE_CREATED = 'credit_note.created'; + const TYPE_CREDIT_NOTE_UPDATED = 'credit_note.updated'; + const TYPE_CREDIT_NOTE_VOIDED = 'credit_note.voided'; + const TYPE_CUSTOMER_CASH_BALANCE_TRANSACTION_CREATED = 'customer_cash_balance_transaction.created'; + const TYPE_CUSTOMER_CREATED = 'customer.created'; + const TYPE_CUSTOMER_DELETED = 'customer.deleted'; + const TYPE_CUSTOMER_DISCOUNT_CREATED = 'customer.discount.created'; + const TYPE_CUSTOMER_DISCOUNT_DELETED = 'customer.discount.deleted'; + const TYPE_CUSTOMER_DISCOUNT_UPDATED = 'customer.discount.updated'; + const TYPE_CUSTOMER_SOURCE_CREATED = 'customer.source.created'; + const TYPE_CUSTOMER_SOURCE_DELETED = 'customer.source.deleted'; + const TYPE_CUSTOMER_SOURCE_EXPIRING = 'customer.source.expiring'; + const TYPE_CUSTOMER_SOURCE_UPDATED = 'customer.source.updated'; + const TYPE_CUSTOMER_SUBSCRIPTION_CREATED = 'customer.subscription.created'; + const TYPE_CUSTOMER_SUBSCRIPTION_DELETED = 'customer.subscription.deleted'; + const TYPE_CUSTOMER_SUBSCRIPTION_PAUSED = 'customer.subscription.paused'; + const TYPE_CUSTOMER_SUBSCRIPTION_PENDING_UPDATE_APPLIED = 'customer.subscription.pending_update_applied'; + const TYPE_CUSTOMER_SUBSCRIPTION_PENDING_UPDATE_EXPIRED = 'customer.subscription.pending_update_expired'; + const TYPE_CUSTOMER_SUBSCRIPTION_RESUMED = 'customer.subscription.resumed'; + const TYPE_CUSTOMER_SUBSCRIPTION_TRIAL_WILL_END = 'customer.subscription.trial_will_end'; + const TYPE_CUSTOMER_SUBSCRIPTION_UPDATED = 'customer.subscription.updated'; + const TYPE_CUSTOMER_TAX_ID_CREATED = 'customer.tax_id.created'; + const TYPE_CUSTOMER_TAX_ID_DELETED = 'customer.tax_id.deleted'; + const TYPE_CUSTOMER_TAX_ID_UPDATED = 'customer.tax_id.updated'; + const TYPE_CUSTOMER_UPDATED = 'customer.updated'; + const TYPE_ENTITLEMENTS_ACTIVE_ENTITLEMENT_SUMMARY_UPDATED = 'entitlements.active_entitlement_summary.updated'; + const TYPE_FILE_CREATED = 'file.created'; + const TYPE_FINANCIAL_CONNECTIONS_ACCOUNT_CREATED = 'financial_connections.account.created'; + const TYPE_FINANCIAL_CONNECTIONS_ACCOUNT_DEACTIVATED = 'financial_connections.account.deactivated'; + const TYPE_FINANCIAL_CONNECTIONS_ACCOUNT_DISCONNECTED = 'financial_connections.account.disconnected'; + const TYPE_FINANCIAL_CONNECTIONS_ACCOUNT_REACTIVATED = 'financial_connections.account.reactivated'; + const TYPE_FINANCIAL_CONNECTIONS_ACCOUNT_REFRESHED_BALANCE = 'financial_connections.account.refreshed_balance'; + const TYPE_FINANCIAL_CONNECTIONS_ACCOUNT_REFRESHED_OWNERSHIP = 'financial_connections.account.refreshed_ownership'; + const TYPE_FINANCIAL_CONNECTIONS_ACCOUNT_REFRESHED_TRANSACTIONS = 'financial_connections.account.refreshed_transactions'; + const TYPE_IDENTITY_VERIFICATION_SESSION_CANCELED = 'identity.verification_session.canceled'; + const TYPE_IDENTITY_VERIFICATION_SESSION_CREATED = 'identity.verification_session.created'; + const TYPE_IDENTITY_VERIFICATION_SESSION_PROCESSING = 'identity.verification_session.processing'; + const TYPE_IDENTITY_VERIFICATION_SESSION_REDACTED = 'identity.verification_session.redacted'; + const TYPE_IDENTITY_VERIFICATION_SESSION_REQUIRES_INPUT = 'identity.verification_session.requires_input'; + const TYPE_IDENTITY_VERIFICATION_SESSION_VERIFIED = 'identity.verification_session.verified'; + const TYPE_INVOICEITEM_CREATED = 'invoiceitem.created'; + const TYPE_INVOICEITEM_DELETED = 'invoiceitem.deleted'; + const TYPE_INVOICE_CREATED = 'invoice.created'; + const TYPE_INVOICE_DELETED = 'invoice.deleted'; + const TYPE_INVOICE_FINALIZATION_FAILED = 'invoice.finalization_failed'; + const TYPE_INVOICE_FINALIZED = 'invoice.finalized'; + const TYPE_INVOICE_MARKED_UNCOLLECTIBLE = 'invoice.marked_uncollectible'; + const TYPE_INVOICE_OVERDUE = 'invoice.overdue'; + const TYPE_INVOICE_PAID = 'invoice.paid'; + const TYPE_INVOICE_PAYMENT_ACTION_REQUIRED = 'invoice.payment_action_required'; + const TYPE_INVOICE_PAYMENT_FAILED = 'invoice.payment_failed'; + const TYPE_INVOICE_PAYMENT_SUCCEEDED = 'invoice.payment_succeeded'; + const TYPE_INVOICE_SENT = 'invoice.sent'; + const TYPE_INVOICE_UPCOMING = 'invoice.upcoming'; + const TYPE_INVOICE_UPDATED = 'invoice.updated'; + const TYPE_INVOICE_VOIDED = 'invoice.voided'; + const TYPE_INVOICE_WILL_BE_DUE = 'invoice.will_be_due'; + const TYPE_ISSUING_AUTHORIZATION_CREATED = 'issuing_authorization.created'; + const TYPE_ISSUING_AUTHORIZATION_REQUEST = 'issuing_authorization.request'; + const TYPE_ISSUING_AUTHORIZATION_UPDATED = 'issuing_authorization.updated'; + const TYPE_ISSUING_CARDHOLDER_CREATED = 'issuing_cardholder.created'; + const TYPE_ISSUING_CARDHOLDER_UPDATED = 'issuing_cardholder.updated'; + const TYPE_ISSUING_CARD_CREATED = 'issuing_card.created'; + const TYPE_ISSUING_CARD_UPDATED = 'issuing_card.updated'; + const TYPE_ISSUING_DISPUTE_CLOSED = 'issuing_dispute.closed'; + const TYPE_ISSUING_DISPUTE_CREATED = 'issuing_dispute.created'; + const TYPE_ISSUING_DISPUTE_FUNDS_REINSTATED = 'issuing_dispute.funds_reinstated'; + const TYPE_ISSUING_DISPUTE_FUNDS_RESCINDED = 'issuing_dispute.funds_rescinded'; + const TYPE_ISSUING_DISPUTE_SUBMITTED = 'issuing_dispute.submitted'; + const TYPE_ISSUING_DISPUTE_UPDATED = 'issuing_dispute.updated'; + const TYPE_ISSUING_PERSONALIZATION_DESIGN_ACTIVATED = 'issuing_personalization_design.activated'; + const TYPE_ISSUING_PERSONALIZATION_DESIGN_DEACTIVATED = 'issuing_personalization_design.deactivated'; + const TYPE_ISSUING_PERSONALIZATION_DESIGN_REJECTED = 'issuing_personalization_design.rejected'; + const TYPE_ISSUING_PERSONALIZATION_DESIGN_UPDATED = 'issuing_personalization_design.updated'; + const TYPE_ISSUING_TOKEN_CREATED = 'issuing_token.created'; + const TYPE_ISSUING_TOKEN_UPDATED = 'issuing_token.updated'; + const TYPE_ISSUING_TRANSACTION_CREATED = 'issuing_transaction.created'; + const TYPE_ISSUING_TRANSACTION_UPDATED = 'issuing_transaction.updated'; + const TYPE_MANDATE_UPDATED = 'mandate.updated'; + const TYPE_PAYMENT_INTENT_AMOUNT_CAPTURABLE_UPDATED = 'payment_intent.amount_capturable_updated'; + const TYPE_PAYMENT_INTENT_CANCELED = 'payment_intent.canceled'; + const TYPE_PAYMENT_INTENT_CREATED = 'payment_intent.created'; + const TYPE_PAYMENT_INTENT_PARTIALLY_FUNDED = 'payment_intent.partially_funded'; + const TYPE_PAYMENT_INTENT_PAYMENT_FAILED = 'payment_intent.payment_failed'; + const TYPE_PAYMENT_INTENT_PROCESSING = 'payment_intent.processing'; + const TYPE_PAYMENT_INTENT_REQUIRES_ACTION = 'payment_intent.requires_action'; + const TYPE_PAYMENT_INTENT_SUCCEEDED = 'payment_intent.succeeded'; + const TYPE_PAYMENT_LINK_CREATED = 'payment_link.created'; + const TYPE_PAYMENT_LINK_UPDATED = 'payment_link.updated'; + const TYPE_PAYMENT_METHOD_ATTACHED = 'payment_method.attached'; + const TYPE_PAYMENT_METHOD_AUTOMATICALLY_UPDATED = 'payment_method.automatically_updated'; + const TYPE_PAYMENT_METHOD_DETACHED = 'payment_method.detached'; + const TYPE_PAYMENT_METHOD_UPDATED = 'payment_method.updated'; + const TYPE_PAYOUT_CANCELED = 'payout.canceled'; + const TYPE_PAYOUT_CREATED = 'payout.created'; + const TYPE_PAYOUT_FAILED = 'payout.failed'; + const TYPE_PAYOUT_PAID = 'payout.paid'; + const TYPE_PAYOUT_RECONCILIATION_COMPLETED = 'payout.reconciliation_completed'; + const TYPE_PAYOUT_UPDATED = 'payout.updated'; + const TYPE_PERSON_CREATED = 'person.created'; + const TYPE_PERSON_DELETED = 'person.deleted'; + const TYPE_PERSON_UPDATED = 'person.updated'; + const TYPE_PLAN_CREATED = 'plan.created'; + const TYPE_PLAN_DELETED = 'plan.deleted'; + const TYPE_PLAN_UPDATED = 'plan.updated'; + const TYPE_PRICE_CREATED = 'price.created'; + const TYPE_PRICE_DELETED = 'price.deleted'; + const TYPE_PRICE_UPDATED = 'price.updated'; + const TYPE_PRODUCT_CREATED = 'product.created'; + const TYPE_PRODUCT_DELETED = 'product.deleted'; + const TYPE_PRODUCT_UPDATED = 'product.updated'; + const TYPE_PROMOTION_CODE_CREATED = 'promotion_code.created'; + const TYPE_PROMOTION_CODE_UPDATED = 'promotion_code.updated'; + const TYPE_QUOTE_ACCEPTED = 'quote.accepted'; + const TYPE_QUOTE_CANCELED = 'quote.canceled'; + const TYPE_QUOTE_CREATED = 'quote.created'; + const TYPE_QUOTE_FINALIZED = 'quote.finalized'; + const TYPE_RADAR_EARLY_FRAUD_WARNING_CREATED = 'radar.early_fraud_warning.created'; + const TYPE_RADAR_EARLY_FRAUD_WARNING_UPDATED = 'radar.early_fraud_warning.updated'; + const TYPE_REFUND_CREATED = 'refund.created'; + const TYPE_REFUND_UPDATED = 'refund.updated'; + const TYPE_REPORTING_REPORT_RUN_FAILED = 'reporting.report_run.failed'; + const TYPE_REPORTING_REPORT_RUN_SUCCEEDED = 'reporting.report_run.succeeded'; + const TYPE_REPORTING_REPORT_TYPE_UPDATED = 'reporting.report_type.updated'; + const TYPE_REVIEW_CLOSED = 'review.closed'; + const TYPE_REVIEW_OPENED = 'review.opened'; + const TYPE_SETUP_INTENT_CANCELED = 'setup_intent.canceled'; + const TYPE_SETUP_INTENT_CREATED = 'setup_intent.created'; + const TYPE_SETUP_INTENT_REQUIRES_ACTION = 'setup_intent.requires_action'; + const TYPE_SETUP_INTENT_SETUP_FAILED = 'setup_intent.setup_failed'; + const TYPE_SETUP_INTENT_SUCCEEDED = 'setup_intent.succeeded'; + const TYPE_SIGMA_SCHEDULED_QUERY_RUN_CREATED = 'sigma.scheduled_query_run.created'; + const TYPE_SOURCE_CANCELED = 'source.canceled'; + const TYPE_SOURCE_CHARGEABLE = 'source.chargeable'; + const TYPE_SOURCE_FAILED = 'source.failed'; + const TYPE_SOURCE_MANDATE_NOTIFICATION = 'source.mandate_notification'; + const TYPE_SOURCE_REFUND_ATTRIBUTES_REQUIRED = 'source.refund_attributes_required'; + const TYPE_SOURCE_TRANSACTION_CREATED = 'source.transaction.created'; + const TYPE_SOURCE_TRANSACTION_UPDATED = 'source.transaction.updated'; + const TYPE_SUBSCRIPTION_SCHEDULE_ABORTED = 'subscription_schedule.aborted'; + const TYPE_SUBSCRIPTION_SCHEDULE_CANCELED = 'subscription_schedule.canceled'; + const TYPE_SUBSCRIPTION_SCHEDULE_COMPLETED = 'subscription_schedule.completed'; + const TYPE_SUBSCRIPTION_SCHEDULE_CREATED = 'subscription_schedule.created'; + const TYPE_SUBSCRIPTION_SCHEDULE_EXPIRING = 'subscription_schedule.expiring'; + const TYPE_SUBSCRIPTION_SCHEDULE_RELEASED = 'subscription_schedule.released'; + const TYPE_SUBSCRIPTION_SCHEDULE_UPDATED = 'subscription_schedule.updated'; + const TYPE_TAX_RATE_CREATED = 'tax_rate.created'; + const TYPE_TAX_RATE_UPDATED = 'tax_rate.updated'; + const TYPE_TAX_SETTINGS_UPDATED = 'tax.settings.updated'; + const TYPE_TERMINAL_READER_ACTION_FAILED = 'terminal.reader.action_failed'; + const TYPE_TERMINAL_READER_ACTION_SUCCEEDED = 'terminal.reader.action_succeeded'; + const TYPE_TEST_HELPERS_TEST_CLOCK_ADVANCING = 'test_helpers.test_clock.advancing'; + const TYPE_TEST_HELPERS_TEST_CLOCK_CREATED = 'test_helpers.test_clock.created'; + const TYPE_TEST_HELPERS_TEST_CLOCK_DELETED = 'test_helpers.test_clock.deleted'; + const TYPE_TEST_HELPERS_TEST_CLOCK_INTERNAL_FAILURE = 'test_helpers.test_clock.internal_failure'; + const TYPE_TEST_HELPERS_TEST_CLOCK_READY = 'test_helpers.test_clock.ready'; + const TYPE_TOPUP_CANCELED = 'topup.canceled'; + const TYPE_TOPUP_CREATED = 'topup.created'; + const TYPE_TOPUP_FAILED = 'topup.failed'; + const TYPE_TOPUP_REVERSED = 'topup.reversed'; + const TYPE_TOPUP_SUCCEEDED = 'topup.succeeded'; + const TYPE_TRANSFER_CREATED = 'transfer.created'; + const TYPE_TRANSFER_REVERSED = 'transfer.reversed'; + const TYPE_TRANSFER_UPDATED = 'transfer.updated'; + const TYPE_TREASURY_CREDIT_REVERSAL_CREATED = 'treasury.credit_reversal.created'; + const TYPE_TREASURY_CREDIT_REVERSAL_POSTED = 'treasury.credit_reversal.posted'; + const TYPE_TREASURY_DEBIT_REVERSAL_COMPLETED = 'treasury.debit_reversal.completed'; + const TYPE_TREASURY_DEBIT_REVERSAL_CREATED = 'treasury.debit_reversal.created'; + const TYPE_TREASURY_DEBIT_REVERSAL_INITIAL_CREDIT_GRANTED = 'treasury.debit_reversal.initial_credit_granted'; + const TYPE_TREASURY_FINANCIAL_ACCOUNT_CLOSED = 'treasury.financial_account.closed'; + const TYPE_TREASURY_FINANCIAL_ACCOUNT_CREATED = 'treasury.financial_account.created'; + const TYPE_TREASURY_FINANCIAL_ACCOUNT_FEATURES_STATUS_UPDATED = 'treasury.financial_account.features_status_updated'; + const TYPE_TREASURY_INBOUND_TRANSFER_CANCELED = 'treasury.inbound_transfer.canceled'; + const TYPE_TREASURY_INBOUND_TRANSFER_CREATED = 'treasury.inbound_transfer.created'; + const TYPE_TREASURY_INBOUND_TRANSFER_FAILED = 'treasury.inbound_transfer.failed'; + const TYPE_TREASURY_INBOUND_TRANSFER_SUCCEEDED = 'treasury.inbound_transfer.succeeded'; + const TYPE_TREASURY_OUTBOUND_PAYMENT_CANCELED = 'treasury.outbound_payment.canceled'; + const TYPE_TREASURY_OUTBOUND_PAYMENT_CREATED = 'treasury.outbound_payment.created'; + const TYPE_TREASURY_OUTBOUND_PAYMENT_EXPECTED_ARRIVAL_DATE_UPDATED = 'treasury.outbound_payment.expected_arrival_date_updated'; + const TYPE_TREASURY_OUTBOUND_PAYMENT_FAILED = 'treasury.outbound_payment.failed'; + const TYPE_TREASURY_OUTBOUND_PAYMENT_POSTED = 'treasury.outbound_payment.posted'; + const TYPE_TREASURY_OUTBOUND_PAYMENT_RETURNED = 'treasury.outbound_payment.returned'; + const TYPE_TREASURY_OUTBOUND_PAYMENT_TRACKING_DETAILS_UPDATED = 'treasury.outbound_payment.tracking_details_updated'; + const TYPE_TREASURY_OUTBOUND_TRANSFER_CANCELED = 'treasury.outbound_transfer.canceled'; + const TYPE_TREASURY_OUTBOUND_TRANSFER_CREATED = 'treasury.outbound_transfer.created'; + const TYPE_TREASURY_OUTBOUND_TRANSFER_EXPECTED_ARRIVAL_DATE_UPDATED = 'treasury.outbound_transfer.expected_arrival_date_updated'; + const TYPE_TREASURY_OUTBOUND_TRANSFER_FAILED = 'treasury.outbound_transfer.failed'; + const TYPE_TREASURY_OUTBOUND_TRANSFER_POSTED = 'treasury.outbound_transfer.posted'; + const TYPE_TREASURY_OUTBOUND_TRANSFER_RETURNED = 'treasury.outbound_transfer.returned'; + const TYPE_TREASURY_OUTBOUND_TRANSFER_TRACKING_DETAILS_UPDATED = 'treasury.outbound_transfer.tracking_details_updated'; + const TYPE_TREASURY_RECEIVED_CREDIT_CREATED = 'treasury.received_credit.created'; + const TYPE_TREASURY_RECEIVED_CREDIT_FAILED = 'treasury.received_credit.failed'; + const TYPE_TREASURY_RECEIVED_CREDIT_SUCCEEDED = 'treasury.received_credit.succeeded'; + const TYPE_TREASURY_RECEIVED_DEBIT_CREATED = 'treasury.received_debit.created'; + + /** + * List events, going back up to 30 days. Each event data is rendered according to + * EDD\Vendor\Stripe API version at its creation time, specified in event object + * api_version attribute (not according to your current EDD\Vendor\Stripe API + * version or Stripe-Version header). + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Event> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an event if it was created in the last 30 days. Supply + * the unique identifier of the event, which you might have received in a webhook. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Event + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Exception/ApiConnectionException.php b/libraries/Stripe/lib/Exception/ApiConnectionException.php new file mode 100644 index 00000000000..9bdfe02cd8c --- /dev/null +++ b/libraries/Stripe/lib/Exception/ApiConnectionException.php @@ -0,0 +1,12 @@ +setHttpStatus($httpStatus); + $instance->setHttpBody($httpBody); + $instance->setJsonBody($jsonBody); + $instance->setHttpHeaders($httpHeaders); + $instance->setStripeCode($stripeCode); + + $instance->setRequestId(null); + if ($httpHeaders && isset($httpHeaders['Request-Id'])) { + $instance->setRequestId($httpHeaders['Request-Id']); + } + + $instance->setError($instance->constructErrorObject()); + + return $instance; + } + + /** + * Gets the EDD\Vendor\Stripe error object. + * + * @return null|\EDD\Vendor\Stripe\ErrorObject + */ + public function getError() + { + return $this->error; + } + + /** + * Sets the EDD\Vendor\Stripe error object. + * + * @param null|\EDD\Vendor\Stripe\ErrorObject $error + */ + public function setError($error) + { + $this->error = $error; + } + + /** + * Gets the HTTP body as a string. + * + * @return null|string + */ + public function getHttpBody() + { + return $this->httpBody; + } + + /** + * Sets the HTTP body as a string. + * + * @param null|string $httpBody + */ + public function setHttpBody($httpBody) + { + $this->httpBody = $httpBody; + } + + /** + * Gets the HTTP headers array. + * + * @return null|array|\EDD\Vendor\Stripe\Util\CaseInsensitiveArray + */ + public function getHttpHeaders() + { + return $this->httpHeaders; + } + + /** + * Sets the HTTP headers array. + * + * @param null|array|\EDD\Vendor\Stripe\Util\CaseInsensitiveArray $httpHeaders + */ + public function setHttpHeaders($httpHeaders) + { + $this->httpHeaders = $httpHeaders; + } + + /** + * Gets the HTTP status code. + * + * @return null|int + */ + public function getHttpStatus() + { + return $this->httpStatus; + } + + /** + * Sets the HTTP status code. + * + * @param null|int $httpStatus + */ + public function setHttpStatus($httpStatus) + { + $this->httpStatus = $httpStatus; + } + + /** + * Gets the JSON deserialized body. + * + * @return null|array + */ + public function getJsonBody() + { + return $this->jsonBody; + } + + /** + * Sets the JSON deserialized body. + * + * @param null|array $jsonBody + */ + public function setJsonBody($jsonBody) + { + $this->jsonBody = $jsonBody; + } + + /** + * Gets the EDD\Vendor\Stripe request ID. + * + * @return null|string + */ + public function getRequestId() + { + return $this->requestId; + } + + /** + * Sets the EDD\Vendor\Stripe request ID. + * + * @param null|string $requestId + */ + public function setRequestId($requestId) + { + $this->requestId = $requestId; + } + + /** + * Gets the EDD\Vendor\Stripe error code. + * + * Cf. the `CODE_*` constants on {@see \EDD\Vendor\Stripe\ErrorObject} for possible + * values. + * + * @return null|string + */ + public function getStripeCode() + { + return $this->stripeCode; + } + + /** + * Sets the EDD\Vendor\Stripe error code. + * + * @param null|string $stripeCode + */ + public function setStripeCode($stripeCode) + { + $this->stripeCode = $stripeCode; + } + + /** + * Returns the string representation of the exception. + * + * @return string + */ + public function __toString() + { + $parentStr = parent::__toString(); + $statusStr = (null === $this->getHttpStatus()) ? '' : "(Status {$this->getHttpStatus()}) "; + $idStr = (null === $this->getRequestId()) ? '' : "(Request {$this->getRequestId()}) "; + + return "Error sending request to Stripe: {$statusStr}{$idStr}{$this->getMessage()}\n{$parentStr}"; + } + + protected function constructErrorObject() + { + if (null === $this->jsonBody || !\array_key_exists('error', $this->jsonBody)) { + return null; + } + + return \EDD\Vendor\Stripe\ErrorObject::constructFrom($this->jsonBody['error']); + } +} diff --git a/libraries/Stripe/lib/Exception/AuthenticationException.php b/libraries/Stripe/lib/Exception/AuthenticationException.php new file mode 100644 index 00000000000..055d17b3bb5 --- /dev/null +++ b/libraries/Stripe/lib/Exception/AuthenticationException.php @@ -0,0 +1,11 @@ +setDeclineCode($declineCode); + $instance->setStripeParam($stripeParam); + + return $instance; + } + + /** + * Gets the decline code. + * + * @return null|string + */ + public function getDeclineCode() + { + return $this->declineCode; + } + + /** + * Sets the decline code. + * + * @param null|string $declineCode + */ + public function setDeclineCode($declineCode) + { + $this->declineCode = $declineCode; + } + + /** + * Gets the parameter related to the error. + * + * @return null|string + */ + public function getStripeParam() + { + return $this->stripeParam; + } + + /** + * Sets the parameter related to the error. + * + * @param null|string $stripeParam + */ + public function setStripeParam($stripeParam) + { + $this->stripeParam = $stripeParam; + } +} diff --git a/libraries/Stripe/lib/Exception/ExceptionInterface.php b/libraries/Stripe/lib/Exception/ExceptionInterface.php new file mode 100644 index 00000000000..51526cab2da --- /dev/null +++ b/libraries/Stripe/lib/Exception/ExceptionInterface.php @@ -0,0 +1,22 @@ +setStripeParam($stripeParam); + + return $instance; + } + + /** + * Gets the parameter related to the error. + * + * @return null|string + */ + public function getStripeParam() + { + return $this->stripeParam; + } + + /** + * Sets the parameter related to the error. + * + * @param null|string $stripeParam + */ + public function setStripeParam($stripeParam) + { + $this->stripeParam = $stripeParam; + } +} diff --git a/libraries/Stripe/lib/Exception/OAuth/ExceptionInterface.php b/libraries/Stripe/lib/Exception/OAuth/ExceptionInterface.php new file mode 100644 index 00000000000..170ef9f3321 --- /dev/null +++ b/libraries/Stripe/lib/Exception/OAuth/ExceptionInterface.php @@ -0,0 +1,10 @@ +jsonBody) { + return null; + } + + return \EDD\Vendor\Stripe\OAuthErrorObject::constructFrom($this->jsonBody); + } +} diff --git a/libraries/Stripe/lib/Exception/OAuth/UnknownOAuthErrorException.php b/libraries/Stripe/lib/Exception/OAuth/UnknownOAuthErrorException.php new file mode 100644 index 00000000000..65659d73721 --- /dev/null +++ b/libraries/Stripe/lib/Exception/OAuth/UnknownOAuthErrorException.php @@ -0,0 +1,12 @@ +setHttpBody($httpBody); + $instance->setSigHeader($sigHeader); + + return $instance; + } + + /** + * Gets the HTTP body as a string. + * + * @return null|string + */ + public function getHttpBody() + { + return $this->httpBody; + } + + /** + * Sets the HTTP body as a string. + * + * @param null|string $httpBody + */ + public function setHttpBody($httpBody) + { + $this->httpBody = $httpBody; + } + + /** + * Gets the `Stripe-Signature` HTTP header. + * + * @return null|string + */ + public function getSigHeader() + { + return $this->sigHeader; + } + + /** + * Sets the `Stripe-Signature` HTTP header. + * + * @param null|string $sigHeader + */ + public function setSigHeader($sigHeader) + { + $this->sigHeader = $sigHeader; + } +} diff --git a/libraries/Stripe/lib/Exception/UnexpectedValueException.php b/libraries/Stripe/lib/Exception/UnexpectedValueException.php new file mode 100644 index 00000000000..bb03ba13ed8 --- /dev/null +++ b/libraries/Stripe/lib/Exception/UnexpectedValueException.php @@ -0,0 +1,7 @@ +ExchangeRate objects allow you to determine the rates that EDD\Vendor\Stripe is currently + * using to convert from one currency to another. Since this number is variable + * throughout the day, there are various reasons why you might want to know the current + * rate (for example, to dynamically price an item for a user with a default + * payment in a foreign currency). + * + * Please refer to our Exchange Rates API guide for more details. + * + * [Note: this integration path is supported but no longer recommended] Additionally, + * you can guarantee that a charge is made with an exchange rate that you expect is + * current. To do so, you must pass in the exchange_rate to charges endpoints. If the + * value is no longer up to date, the charge won't go through. Please refer to our + * Using with charges guide for more details. + * + * ----- + * + * + * + * This Exchange Rates API is a Beta Service and is subject to Stripe's terms of service. You may use the API solely for the purpose of transacting on Stripe. For example, the API may be queried in order to: + * + * - localize prices for processing payments on Stripe + * - reconcile EDD\Vendor\Stripe transactions + * - determine how much money to send to a connected account + * - determine app fees to charge a connected account + * + * Using this Exchange Rates API beta for any purpose other than to transact on EDD\Vendor\Stripe is strictly prohibited and constitutes a violation of Stripe's terms of service. + * + * @property string $id Unique identifier for the object. Represented as the three-letter ISO currency code in lowercase. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property \EDD\Vendor\Stripe\StripeObject $rates Hash where the keys are supported currencies and the values are the exchange rate at which the base id currency converts to the key currency. + */ +class ExchangeRate extends ApiResource +{ + const OBJECT_NAME = 'exchange_rate'; + + /** + * Returns a list of objects that contain the rates at which foreign currencies are + * converted to one another. Only shows the currencies for which EDD\Vendor\Stripe supports. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ExchangeRate> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the exchange rates from the given currency to every supported + * currency. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ExchangeRate + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/File.php b/libraries/Stripe/lib/File.php new file mode 100644 index 00000000000..773d33cfe42 --- /dev/null +++ b/libraries/Stripe/lib/File.php @@ -0,0 +1,119 @@ +create file request + * (for example, when uploading dispute evidence). EDD\Vendor\Stripe also + * creates files independently (for example, the results of a Sigma scheduled + * query). + * + * Related guide: File upload guide + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|int $expires_at The file expires and isn't available at this time in epoch seconds. + * @property null|string $filename The suitable name for saving the file to a filesystem. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\FileLink> $links A list of file links that point at this file. + * @property string $purpose The purpose of the uploaded file. + * @property int $size The size of the file object in bytes. + * @property null|string $title A suitable title for the document. + * @property null|string $type The returned file type (for example, csv, pdf, jpg, or png). + * @property null|string $url Use your live secret API key to download the file from this URL. + */ +class File extends ApiResource +{ + const OBJECT_NAME = 'file'; + + const PURPOSE_ACCOUNT_REQUIREMENT = 'account_requirement'; + const PURPOSE_ADDITIONAL_VERIFICATION = 'additional_verification'; + const PURPOSE_BUSINESS_ICON = 'business_icon'; + const PURPOSE_BUSINESS_LOGO = 'business_logo'; + const PURPOSE_CUSTOMER_SIGNATURE = 'customer_signature'; + const PURPOSE_DISPUTE_EVIDENCE = 'dispute_evidence'; + const PURPOSE_DOCUMENT_PROVIDER_IDENTITY_DOCUMENT = 'document_provider_identity_document'; + const PURPOSE_FINANCE_REPORT_RUN = 'finance_report_run'; + const PURPOSE_IDENTITY_DOCUMENT = 'identity_document'; + const PURPOSE_IDENTITY_DOCUMENT_DOWNLOADABLE = 'identity_document_downloadable'; + const PURPOSE_ISSUING_REGULATORY_REPORTING = 'issuing_regulatory_reporting'; + const PURPOSE_PCI_DOCUMENT = 'pci_document'; + const PURPOSE_SELFIE = 'selfie'; + const PURPOSE_SIGMA_SCHEDULED_QUERY = 'sigma_scheduled_query'; + const PURPOSE_TAX_DOCUMENT_USER_UPLOAD = 'tax_document_user_upload'; + const PURPOSE_TERMINAL_READER_SPLASHSCREEN = 'terminal_reader_splashscreen'; + + /** + * Returns a list of the files that your account has access to. EDD\Vendor\Stripe sorts and + * returns the files by their creation dates, placing the most recently created + * files at the top. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\File> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing file object. After you supply a unique file + * ID, EDD\Vendor\Stripe returns the corresponding file object. Learn how to access file contents. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\File + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + // This resource can have two different object names. In latter API + // versions, only `file` is used, but since stripe-php may be used with + // any API version, we need to support deserializing the older + // `file_upload` object into the same class. + const OBJECT_NAME_ALT = 'file_upload'; + + use ApiOperations\Create { + create as protected _create; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\File the created file + */ + public static function create($params = null, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + if (null === $opts->apiBase) { + $opts->apiBase = Stripe::$apiUploadBase; + } + // Manually flatten params, otherwise curl's multipart encoder will + // choke on nested arrays. + $flatParams = \array_column(\EDD\Vendor\Stripe\Util\Util::flattenParams($params), 1, 0); + + return static::_create($flatParams, $opts); + } +} diff --git a/libraries/Stripe/lib/FileLink.php b/libraries/Stripe/lib/FileLink.php new file mode 100644 index 00000000000..caf57ec45a0 --- /dev/null +++ b/libraries/Stripe/lib/FileLink.php @@ -0,0 +1,108 @@ +File object with non-EDD\Vendor\Stripe users, you can + * create a FileLink. FileLinks contain a URL that you can use to + * retrieve the contents of the file without authentication. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property bool $expired Returns if the link is already expired. + * @property null|int $expires_at Time that the link expires. + * @property string|\EDD\Vendor\Stripe\File $file The file object this link points to. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $url The publicly accessible URL to download the file. + */ +class FileLink extends ApiResource +{ + const OBJECT_NAME = 'file_link'; + + use ApiOperations\Update; + + /** + * Creates a new file link object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FileLink the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of file links. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\FileLink> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the file link with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FileLink + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates an existing file link object. Expired links can no longer be updated. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FileLink the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/FinancialConnections/Account.php b/libraries/Stripe/lib/FinancialConnections/Account.php new file mode 100644 index 00000000000..75ebe06e8fa --- /dev/null +++ b/libraries/Stripe/lib/FinancialConnections/Account.php @@ -0,0 +1,172 @@ +subcategory. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $display_name A human-readable name that has been assigned to this account, either by the account holder or by the institution. + * @property string $institution_name The name of the institution that holds this account. + * @property null|string $last4 The last 4 digits of the account number. If present, this will be 4 numeric characters. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string|\EDD\Vendor\Stripe\FinancialConnections\AccountOwnership $ownership The most recent information about the account's owners. + * @property null|\EDD\Vendor\Stripe\StripeObject $ownership_refresh The state of the most recent attempt to refresh the account owners. + * @property null|string[] $permissions The list of permissions granted by this account. + * @property string $status The status of the link to the account. + * @property string $subcategory

    If category is cash, one of:

    - checking - savings - other

    If category is credit, one of:

    - mortgage - line_of_credit - credit_card - other

    If category is investment or other, this will be other.

    + * @property null|string[] $subscriptions The list of data refresh subscriptions requested on this account. + * @property string[] $supported_payment_method_types The PaymentMethod type(s) that can be created from this account. + * @property null|\EDD\Vendor\Stripe\StripeObject $transaction_refresh The state of the most recent attempt to refresh the account transactions. + */ +class Account extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'financial_connections.account'; + + const CATEGORY_CASH = 'cash'; + const CATEGORY_CREDIT = 'credit'; + const CATEGORY_INVESTMENT = 'investment'; + const CATEGORY_OTHER = 'other'; + + const STATUS_ACTIVE = 'active'; + const STATUS_DISCONNECTED = 'disconnected'; + const STATUS_INACTIVE = 'inactive'; + + const SUBCATEGORY_CHECKING = 'checking'; + const SUBCATEGORY_CREDIT_CARD = 'credit_card'; + const SUBCATEGORY_LINE_OF_CREDIT = 'line_of_credit'; + const SUBCATEGORY_MORTGAGE = 'mortgage'; + const SUBCATEGORY_OTHER = 'other'; + const SUBCATEGORY_SAVINGS = 'savings'; + + /** + * Returns a list of Financial Connections Account objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\FinancialConnections\Account> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an Financial Connections Account. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account the disconnected account + */ + public function disconnect($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/disconnect'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\FinancialConnections\AccountOwner> list of account owners + */ + public static function allOwners($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/owners'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account the refreshed account + */ + public function refreshAccount($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/refresh'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account the subscribed account + */ + public function subscribe($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/subscribe'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account the unsubscribed account + */ + public function unsubscribe($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/unsubscribe'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/FinancialConnections/AccountOwner.php b/libraries/Stripe/lib/FinancialConnections/AccountOwner.php new file mode 100644 index 00000000000..3fb8368a9e0 --- /dev/null +++ b/libraries/Stripe/lib/FinancialConnections/AccountOwner.php @@ -0,0 +1,22 @@ + $owners A paginated list of owners for this account. + */ +class AccountOwnership extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'financial_connections.account_ownership'; +} diff --git a/libraries/Stripe/lib/FinancialConnections/Session.php b/libraries/Stripe/lib/FinancialConnections/Session.php new file mode 100644 index 00000000000..8c87fad2d30 --- /dev/null +++ b/libraries/Stripe/lib/FinancialConnections/Session.php @@ -0,0 +1,67 @@ + $accounts The accounts that were collected as part of this Session. + * @property string $client_secret A value that will be passed to the client to launch the authentication flow. + * @property null|\EDD\Vendor\Stripe\StripeObject $filters + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string[] $permissions Permissions requested for accounts collected during this session. + * @property null|string[] $prefetch Data features requested to be retrieved upon account creation. + * @property null|string $return_url For webview integrations only. Upon completing OAuth login in the native browser, the user will be redirected to this URL to return to your app. + */ +class Session extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'financial_connections.session'; + + /** + * To launch the Financial Connections authorization flow, create a + * Session. The session’s client_secret can be used to + * launch the flow using Stripe.js. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Session the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Retrieves the details of a Financial Connections Session. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Session + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/FinancialConnections/Transaction.php b/libraries/Stripe/lib/FinancialConnections/Transaction.php new file mode 100644 index 00000000000..b1ca553201e --- /dev/null +++ b/libraries/Stripe/lib/FinancialConnections/Transaction.php @@ -0,0 +1,66 @@ +ISO currency code, in lowercase. Must be a supported currency. + * @property string $description The description of this transaction. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $status The status of the transaction. + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property int $transacted_at Time at which the transaction was transacted. Measured in seconds since the Unix epoch. + * @property string $transaction_refresh The token of the transaction refresh that last updated or created this transaction. + * @property int $updated Time at which the object was last updated. Measured in seconds since the Unix epoch. + */ +class Transaction extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'financial_connections.transaction'; + + const STATUS_PENDING = 'pending'; + const STATUS_POSTED = 'posted'; + const STATUS_VOID = 'void'; + + /** + * Returns a list of Financial Connections Transaction objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\FinancialConnections\Transaction> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a Financial Connections Transaction. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Transaction + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Forwarding/Request.php b/libraries/Stripe/lib/Forwarding/Request.php new file mode 100644 index 00000000000..1d03ef1e23f --- /dev/null +++ b/libraries/Stripe/lib/Forwarding/Request.php @@ -0,0 +1,97 @@ +Forward card details to third-party API endpoints. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $payment_method The PaymentMethod to insert into the forwarded request. Forwarding previously consumed PaymentMethods is allowed. + * @property string[] $replacements The field kinds to be replaced in the forwarded request. + * @property null|\EDD\Vendor\Stripe\StripeObject $request_context Context about the request from Stripe's servers to the destination endpoint. + * @property null|\EDD\Vendor\Stripe\StripeObject $request_details The request that was sent to the destination endpoint. We redact any sensitive fields. + * @property null|\EDD\Vendor\Stripe\StripeObject $response_details The response that the destination endpoint returned to us. We redact any sensitive fields. + * @property null|string $url The destination URL for the forwarded request. Must be supported by the config. + */ +class Request extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'forwarding.request'; + + /** + * Creates a ForwardingRequest object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Forwarding\Request the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Lists all ForwardingRequest objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Forwarding\Request> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a ForwardingRequest object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Forwarding\Request + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/FundingInstructions.php b/libraries/Stripe/lib/FundingInstructions.php new file mode 100644 index 00000000000..264adcc2d93 --- /dev/null +++ b/libraries/Stripe/lib/FundingInstructions.php @@ -0,0 +1,25 @@ +balance that is + * automatically applied to future invoices and payments using the customer_balance payment method. + * Customers can fund this balance by initiating a bank transfer to any account in the + * financial_addresses field. + * Related guide: Customer balance funding instructions. + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property \EDD\Vendor\Stripe\StripeObject $bank_transfer + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string $funding_type The funding_type of the returned instructions + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + */ +class FundingInstructions extends ApiResource +{ + const OBJECT_NAME = 'funding_instructions'; + + const FUNDING_TYPE_BANK_TRANSFER = 'bank_transfer'; +} diff --git a/libraries/Stripe/lib/HttpClient/ClientInterface.php b/libraries/Stripe/lib/HttpClient/ClientInterface.php new file mode 100644 index 00000000000..efac4f53b5e --- /dev/null +++ b/libraries/Stripe/lib/HttpClient/ClientInterface.php @@ -0,0 +1,22 @@ +defaultOptions = $defaultOptions; + $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator(); + $this->initUserAgentInfo(); + + $this->enableHttp2 = $this->canSafelyUseHttp2(); + } + + public function __destruct() + { + $this->closeCurlHandle(); + } + + public function initUserAgentInfo() + { + $curlVersion = \curl_version(); + $this->userAgentInfo = [ + 'httplib' => 'curl ' . $curlVersion['version'], + 'ssllib' => $curlVersion['ssl_version'], + ]; + } + + public function getDefaultOptions() + { + return $this->defaultOptions; + } + + public function getUserAgentInfo() + { + return $this->userAgentInfo; + } + + /** + * @return bool + */ + public function getEnablePersistentConnections() + { + return $this->enablePersistentConnections; + } + + /** + * @param bool $enable + */ + public function setEnablePersistentConnections($enable) + { + $this->enablePersistentConnections = $enable; + } + + /** + * @return bool + */ + public function getEnableHttp2() + { + return $this->enableHttp2; + } + + /** + * @param bool $enable + */ + public function setEnableHttp2($enable) + { + $this->enableHttp2 = $enable; + } + + /** + * @return null|callable + */ + public function getRequestStatusCallback() + { + return $this->requestStatusCallback; + } + + /** + * Sets a callback that is called after each request. The callback will + * receive the following parameters: + *
      + *
    1. string $rbody The response body
    2. + *
    3. integer $rcode The response status code
    4. + *
    5. \EDD\Vendor\Stripe\Util\CaseInsensitiveArray $rheaders The response headers
    6. + *
    7. integer $errno The curl error number
    8. + *
    9. string|null $message The curl error message
    10. + *
    11. boolean $shouldRetry Whether the request will be retried
    12. + *
    13. integer $numRetries The number of the retry attempt
    14. + *
    . + * + * @param null|callable $requestStatusCallback + */ + public function setRequestStatusCallback($requestStatusCallback) + { + $this->requestStatusCallback = $requestStatusCallback; + } + + // USER DEFINED TIMEOUTS + + const DEFAULT_TIMEOUT = 80; + const DEFAULT_CONNECT_TIMEOUT = 30; + + private $timeout = self::DEFAULT_TIMEOUT; + private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT; + + public function setTimeout($seconds) + { + $this->timeout = (int) \max($seconds, 0); + + return $this; + } + + public function setConnectTimeout($seconds) + { + $this->connectTimeout = (int) \max($seconds, 0); + + return $this; + } + + public function getTimeout() + { + return $this->timeout; + } + + public function getConnectTimeout() + { + return $this->connectTimeout; + } + + // END OF USER DEFINED TIMEOUTS + + private function constructRequest($method, $absUrl, $headers, $params, $hasFile) + { + $method = \strtolower($method); + + $opts = []; + if (\is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value + $opts = \call_user_func_array($this->defaultOptions, \func_get_args()); + if (!\is_array($opts)) { + throw new Exception\UnexpectedValueException('Non-array value returned by defaultOptions CurlClient callback'); + } + } elseif (\is_array($this->defaultOptions)) { // set default curlopts from array + $opts = $this->defaultOptions; + } + + $params = Util\Util::objectsToIds($params); + + if ('get' === $method) { + if ($hasFile) { + throw new Exception\UnexpectedValueException( + 'Issuing a GET request with a file parameter' + ); + } + $opts[\CURLOPT_HTTPGET] = 1; + if (\count($params) > 0) { + $encoded = Util\Util::encodeParameters($params); + $absUrl = "{$absUrl}?{$encoded}"; + } + } elseif ('post' === $method) { + $opts[\CURLOPT_POST] = 1; + $opts[\CURLOPT_POSTFIELDS] = $hasFile ? $params : Util\Util::encodeParameters($params); + } elseif ('delete' === $method) { + $opts[\CURLOPT_CUSTOMREQUEST] = 'DELETE'; + if (\count($params) > 0) { + $encoded = Util\Util::encodeParameters($params); + $absUrl = "{$absUrl}?{$encoded}"; + } + } else { + throw new Exception\UnexpectedValueException("Unrecognized method {$method}"); + } + + // It is only safe to retry network failures on POST requests if we + // add an Idempotency-Key header + if (('post' === $method) && (Stripe::$maxNetworkRetries > 0)) { + if (!$this->hasHeader($headers, 'Idempotency-Key')) { + $headers[] = 'Idempotency-Key: ' . $this->randomGenerator->uuid(); + } + } + + // By default for large request body sizes (> 1024 bytes), cURL will + // send a request without a body and with a `Expect: 100-continue` + // header, which gives the server a chance to respond with an error + // status code in cases where one can be determined right away (say + // on an authentication problem for example), and saves the "large" + // request body from being ever sent. + // + // Unfortunately, the bindings don't currently correctly handle the + // success case (in which the server sends back a 100 CONTINUE), so + // we'll error under that condition. To compensate for that problem + // for the time being, override cURL's behavior by simply always + // sending an empty `Expect:` header. + $headers[] = 'Expect: '; + + $absUrl = Util\Util::utf8($absUrl); + $opts[\CURLOPT_URL] = $absUrl; + $opts[\CURLOPT_RETURNTRANSFER] = true; + $opts[\CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout; + $opts[\CURLOPT_TIMEOUT] = $this->timeout; + $opts[\CURLOPT_HTTPHEADER] = $headers; + $opts[\CURLOPT_CAINFO] = Stripe::getCABundlePath(); + if (!Stripe::getVerifySslCerts()) { + $opts[\CURLOPT_SSL_VERIFYPEER] = false; + } + + if (!isset($opts[\CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) { + // For HTTPS requests, enable HTTP/2, if supported + $opts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2TLS; + } + + return [$opts, $absUrl]; + } + + public function request($method, $absUrl, $headers, $params, $hasFile) + { + list($opts, $absUrl) = $this->constructRequest($method, $absUrl, $headers, $params, $hasFile); + + list($rbody, $rcode, $rheaders) = $this->executeRequestWithRetries($opts, $absUrl); + + return [$rbody, $rcode, $rheaders]; + } + + public function requestStream($method, $absUrl, $headers, $params, $hasFile, $readBodyChunk) + { + list($opts, $absUrl) = $this->constructRequest($method, $absUrl, $headers, $params, $hasFile); + + $opts[\CURLOPT_RETURNTRANSFER] = false; + list($rbody, $rcode, $rheaders) = $this->executeStreamingRequestWithRetries($opts, $absUrl, $readBodyChunk); + + return [$rbody, $rcode, $rheaders]; + } + + /** + * Curl permits sending \CURLOPT_HEADERFUNCTION, which is called with lines + * from the header and \CURLOPT_WRITEFUNCTION, which is called with bytes + * from the body. You usually want to handle the body differently depending + * on what was in the header. + * + * This function makes it easier to specify different callbacks depending + * on the contents of the heeder. After the header has been completely read + * and the body begins to stream, it will call $determineWriteCallback with + * the array of headers. $determineWriteCallback should, based on the + * headers it receives, return a "writeCallback" that describes what to do + * with the incoming HTTP response body. + * + * @param array $opts + * @param callable $determineWriteCallback + * + * @return array + */ + private function useHeadersToDetermineWriteCallback($opts, $determineWriteCallback) + { + $rheaders = new Util\CaseInsensitiveArray(); + $headerCallback = function ($curl, $header_line) use (&$rheaders) { + return self::parseLineIntoHeaderArray($header_line, $rheaders); + }; + + $writeCallback = null; + $writeCallbackWrapper = function ($curl, $data) use (&$writeCallback, &$rheaders, &$determineWriteCallback) { + if (null === $writeCallback) { + $writeCallback = \call_user_func_array($determineWriteCallback, [$rheaders]); + } + + return \call_user_func_array($writeCallback, [$curl, $data]); + }; + + return [$headerCallback, $writeCallbackWrapper]; + } + + private static function parseLineIntoHeaderArray($line, &$headers) + { + if (false === \strpos($line, ':')) { + return \strlen($line); + } + list($key, $value) = \explode(':', \trim($line), 2); + $headers[\trim($key)] = \trim($value); + + return \strlen($line); + } + + /** + * Like `executeRequestWithRetries` except: + * 1. Does not buffer the body of a successful (status code < 300) + * response into memory -- instead, calls the caller-provided + * $readBodyChunk with each chunk of incoming data. + * 2. Does not retry if a network error occurs while streaming the + * body of a successful response. + * + * @param array $opts cURL options + * @param string $absUrl + * @param callable $readBodyChunk + * + * @return array + */ + public function executeStreamingRequestWithRetries($opts, $absUrl, $readBodyChunk) + { + /** @var bool */ + $shouldRetry = false; + /** @var int */ + $numRetries = 0; + + // Will contain the bytes of the body of the last request + // if it was not successful and should not be retries + /** @var null|string */ + $rbody = null; + + // Status code of the last request + /** @var null|bool */ + $rcode = null; + + // Array of headers from the last request + /** @var null|array */ + $lastRHeaders = null; + + $errno = null; + $message = null; + + $determineWriteCallback = function ($rheaders) use ( + &$readBodyChunk, + &$shouldRetry, + &$rbody, + &$numRetries, + &$rcode, + &$lastRHeaders, + &$errno + ) { + $lastRHeaders = $rheaders; + $errno = \curl_errno($this->curlHandle); + + $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE); + + // Send the bytes from the body of a successful request to the caller-provided $readBodyChunk. + if ($rcode < 300) { + $rbody = null; + + return function ($curl, $data) use (&$readBodyChunk) { + // Don't expose the $curl handle to the user, and don't require them to + // return the length of $data. + \call_user_func_array($readBodyChunk, [$data]); + + return \strlen($data); + }; + } + + $shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries); + + // Discard the body from an unsuccessful request that should be retried. + if ($shouldRetry) { + return function ($curl, $data) { + return \strlen($data); + }; + } else { + // Otherwise, buffer the body into $rbody. It will need to be parsed to determine + // which exception to throw to the user. + $rbody = ''; + + return function ($curl, $data) use (&$rbody) { + $rbody .= $data; + + return \strlen($data); + }; + } + }; + + while (true) { + list($headerCallback, $writeCallback) = $this->useHeadersToDetermineWriteCallback($opts, $determineWriteCallback); + $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback; + $opts[\CURLOPT_WRITEFUNCTION] = $writeCallback; + + $shouldRetry = false; + $rbody = null; + $this->resetCurlHandle(); + \curl_setopt_array($this->curlHandle, $opts); + $result = \curl_exec($this->curlHandle); + $errno = \curl_errno($this->curlHandle); + if (0 !== $errno) { + $message = \curl_error($this->curlHandle); + } + if (!$this->getEnablePersistentConnections()) { + $this->closeCurlHandle(); + } + + if (\is_callable($this->getRequestStatusCallback())) { + \call_user_func_array( + $this->getRequestStatusCallback(), + [$rbody, $rcode, $lastRHeaders, $errno, $message, $shouldRetry, $numRetries] + ); + } + + if ($shouldRetry) { + ++$numRetries; + $sleepSeconds = $this->sleepTime($numRetries, $lastRHeaders); + \usleep((int) ($sleepSeconds * 1000000)); + } else { + break; + } + } + + if (0 !== $errno) { + $this->handleCurlError($absUrl, $errno, $message, $numRetries); + } + + return [$rbody, $rcode, $lastRHeaders]; + } + + /** + * @param array $opts cURL options + * @param string $absUrl + */ + public function executeRequestWithRetries($opts, $absUrl) + { + $numRetries = 0; + + while (true) { + $rcode = 0; + $errno = 0; + $message = null; + + // Create a callback to capture HTTP headers for the response + $rheaders = new Util\CaseInsensitiveArray(); + $headerCallback = function ($curl, $header_line) use (&$rheaders) { + return CurlClient::parseLineIntoHeaderArray($header_line, $rheaders); + }; + $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback; + + $this->resetCurlHandle(); + \curl_setopt_array($this->curlHandle, $opts); + $rbody = \curl_exec($this->curlHandle); + + if (false === $rbody) { + $errno = \curl_errno($this->curlHandle); + $message = \curl_error($this->curlHandle); + } else { + $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE); + } + if (!$this->getEnablePersistentConnections()) { + $this->closeCurlHandle(); + } + + $shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries); + + if (\is_callable($this->getRequestStatusCallback())) { + \call_user_func_array( + $this->getRequestStatusCallback(), + [$rbody, $rcode, $rheaders, $errno, $message, $shouldRetry, $numRetries] + ); + } + + if ($shouldRetry) { + ++$numRetries; + $sleepSeconds = $this->sleepTime($numRetries, $rheaders); + \usleep((int) ($sleepSeconds * 1000000)); + } else { + break; + } + } + + if (false === $rbody) { + $this->handleCurlError($absUrl, $errno, $message, $numRetries); + } + + return [$rbody, $rcode, $rheaders]; + } + + /** + * @param string $url + * @param int $errno + * @param string $message + * @param int $numRetries + * + * @throws Exception\ApiConnectionException + */ + private function handleCurlError($url, $errno, $message, $numRetries) + { + switch ($errno) { + case \CURLE_COULDNT_CONNECT: + case \CURLE_COULDNT_RESOLVE_HOST: + case \CURLE_OPERATION_TIMEOUTED: + $msg = "Could not connect to EDD\Vendor\Stripe ({$url}). Please check your " + . 'internet connection and try again. If this problem persists, ' + . "you should check Stripe's service status at " + . 'https://twitter.com/stripestatus, or'; + + break; + + case \CURLE_SSL_CACERT: + case \CURLE_SSL_PEER_CERTIFICATE: + $msg = "Could not verify Stripe's SSL certificate. Please make sure " + . 'that your network is not intercepting certificates. ' + . "(Try going to {$url} in your browser.) " + . 'If this problem persists,'; + + break; + + default: + $msg = 'Unexpected error communicating with Stripe. ' + . 'If this problem persists,'; + } + $msg .= ' let us know at support@stripe.com.'; + + $msg .= "\n\n(Network error [errno {$errno}]: {$message})"; + + if ($numRetries > 0) { + $msg .= "\n\nRequest was retried {$numRetries} times."; + } + + throw new Exception\ApiConnectionException($msg); + } + + /** + * Checks if an error is a problem that we should retry on. This includes both + * socket errors that may represent an intermittent problem and some special + * HTTP statuses. + * + * @param int $errno + * @param int $rcode + * @param array|\EDD\Vendor\Stripe\Util\CaseInsensitiveArray $rheaders + * @param int $numRetries + * + * @return bool + */ + private function shouldRetry($errno, $rcode, $rheaders, $numRetries) + { + if ($numRetries >= Stripe::getMaxNetworkRetries()) { + return false; + } + + // Retry on timeout-related problems (either on open or read). + if (\CURLE_OPERATION_TIMEOUTED === $errno) { + return true; + } + + // Destination refused the connection, the connection was reset, or a + // variety of other connection failures. This could occur from a single + // saturated server, so retry in case it's intermittent. + if (\CURLE_COULDNT_CONNECT === $errno) { + return true; + } + + // The API may ask us not to retry (eg; if doing so would be a no-op) + // or advise us to retry (eg; in cases of lock timeouts); we defer to that. + if (isset($rheaders['stripe-should-retry'])) { + if ('false' === $rheaders['stripe-should-retry']) { + return false; + } + if ('true' === $rheaders['stripe-should-retry']) { + return true; + } + } + + // 409 Conflict + if (409 === $rcode) { + return true; + } + + // Retry on 500, 503, and other internal errors. + // + // Note that we expect the stripe-should-retry header to be false + // in most cases when a 500 is returned, since our idempotency framework + // would typically replay it anyway. + if ($rcode >= 500) { + return true; + } + + return false; + } + + /** + * Provides the number of seconds to wait before retrying a request. + * + * @param int $numRetries + * @param array|\EDD\Vendor\Stripe\Util\CaseInsensitiveArray $rheaders + * + * @return int + */ + private function sleepTime($numRetries, $rheaders) + { + // Apply exponential backoff with $initialNetworkRetryDelay on the + // number of $numRetries so far as inputs. Do not allow the number to exceed + // $maxNetworkRetryDelay. + $sleepSeconds = \min( + Stripe::getInitialNetworkRetryDelay() * 1.0 * 2 ** ($numRetries - 1), + Stripe::getMaxNetworkRetryDelay() + ); + + // Apply some jitter by randomizing the value in the range of + // ($sleepSeconds / 2) to ($sleepSeconds). + $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat()); + + // But never sleep less than the base sleep seconds. + $sleepSeconds = \max(Stripe::getInitialNetworkRetryDelay(), $sleepSeconds); + + // And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask. + $retryAfter = isset($rheaders['retry-after']) ? (float) ($rheaders['retry-after']) : 0.0; + if (\floor($retryAfter) === $retryAfter && $retryAfter <= Stripe::getMaxRetryAfter()) { + $sleepSeconds = \max($sleepSeconds, $retryAfter); + } + + return $sleepSeconds; + } + + /** + * Initializes the curl handle. If already initialized, the handle is closed first. + */ + private function initCurlHandle() + { + $this->closeCurlHandle(); + $this->curlHandle = \curl_init(); + } + + /** + * Closes the curl handle if initialized. Do nothing if already closed. + */ + private function closeCurlHandle() + { + if (null !== $this->curlHandle) { + \curl_close($this->curlHandle); + $this->curlHandle = null; + } + } + + /** + * Resets the curl handle. If the handle is not already initialized, or if persistent + * connections are disabled, the handle is reinitialized instead. + */ + private function resetCurlHandle() + { + if (null !== $this->curlHandle && $this->getEnablePersistentConnections()) { + \curl_reset($this->curlHandle); + } else { + $this->initCurlHandle(); + } + } + + /** + * Indicates whether it is safe to use HTTP/2 or not. + * + * @return bool + */ + private function canSafelyUseHttp2() + { + // Versions of curl older than 7.60.0 don't respect GOAWAY frames + // (cf. https://github.com/curl/curl/issues/2416), which EDD\Vendor\Stripe use. + $curlVersion = \curl_version()['version']; + + return \version_compare($curlVersion, '7.60.0') >= 0; + } + + /** + * Checks if a list of headers contains a specific header name. + * + * @param string[] $headers + * @param string $name + * + * @return bool + */ + private function hasHeader($headers, $name) + { + foreach ($headers as $header) { + if (0 === \strncasecmp($header, "{$name}: ", \strlen($name) + 2)) { + return true; + } + } + + return false; + } +} diff --git a/libraries/Stripe/lib/HttpClient/StreamingClientInterface.php b/libraries/Stripe/lib/HttpClient/StreamingClientInterface.php new file mode 100644 index 00000000000..e68e03a41c9 --- /dev/null +++ b/libraries/Stripe/lib/HttpClient/StreamingClientInterface.php @@ -0,0 +1,23 @@ +type and options + * parameters used. You can find the result of each verification check performed in the + * appropriate sub-resource: document, id_number, selfie. + * + * Each VerificationReport contains a copy of any data collected by the user as well as + * reference IDs which can be used to access collected images through the FileUpload + * API. To configure and create VerificationReports, use the + * VerificationSession API. + * + * Related guide: Accessing verification results. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $client_reference_id A string to reference this user. This can be a customer ID, a session ID, or similar, and can be used to reconcile this verification with your internal systems. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|\EDD\Vendor\Stripe\StripeObject $document Result from a document check + * @property null|\EDD\Vendor\Stripe\StripeObject $email Result from a email check + * @property null|\EDD\Vendor\Stripe\StripeObject $id_number Result from an id_number check + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $options + * @property null|\EDD\Vendor\Stripe\StripeObject $phone Result from a phone check + * @property null|\EDD\Vendor\Stripe\StripeObject $selfie Result from a selfie check + * @property string $type Type of report. + * @property null|string $verification_flow The configuration token of a Verification Flow from the dashboard. + * @property null|string $verification_session ID of the VerificationSession that created this report. + */ +class VerificationReport extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'identity.verification_report'; + + const TYPE_DOCUMENT = 'document'; + const TYPE_ID_NUMBER = 'id_number'; + const TYPE_VERIFICATION_FLOW = 'verification_flow'; + + /** + * List all verification reports. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Identity\VerificationReport> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an existing VerificationReport. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationReport + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Identity/VerificationSession.php b/libraries/Stripe/lib/Identity/VerificationSession.php new file mode 100644 index 00000000000..6d8d404a6ae --- /dev/null +++ b/libraries/Stripe/lib/Identity/VerificationSession.php @@ -0,0 +1,185 @@ +verification + * check to perform. Only create one VerificationSession for + * each verification in your system. + * + * A VerificationSession transitions through multiple + * statuses throughout its lifetime as it progresses through + * the verification flow. The VerificationSession contains the user's verified data after + * verification checks are complete. + * + * Related guide: The Verification Sessions API + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $client_reference_id A string to reference this user. This can be a customer ID, a session ID, or similar, and can be used to reconcile this verification with your internal systems. + * @property null|string $client_secret The short-lived client secret used by Stripe.js to show a verification modal inside your app. This client secret expires after 24 hours and can only be used once. Don’t store it, log it, embed it in a URL, or expose it to anyone other than the user. Make sure that you have TLS enabled on any page that includes the client secret. Refer to our docs on passing the client secret to the frontend to learn more. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|\EDD\Vendor\Stripe\StripeObject $last_error If present, this property tells you the last error encountered when processing the verification. + * @property null|string|\EDD\Vendor\Stripe\Identity\VerificationReport $last_verification_report ID of the most recent VerificationReport. Learn more about accessing detailed verification results. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $options A set of options for the session’s verification checks. + * @property null|\EDD\Vendor\Stripe\StripeObject $provided_details Details provided about the user being verified. These details may be shown to the user. + * @property null|\EDD\Vendor\Stripe\StripeObject $redaction Redaction status of this VerificationSession. If the VerificationSession is not redacted, this field will be null. + * @property null|string $related_customer Token referencing a Customer resource. + * @property string $status Status of this VerificationSession. Learn more about the lifecycle of sessions. + * @property string $type The type of verification check to be performed. + * @property null|string $url The short-lived URL that you use to redirect a user to EDD\Vendor\Stripe to submit their identity information. This URL expires after 48 hours and can only be used once. Don’t store it, log it, send it in emails or expose it to anyone other than the user. Refer to our docs on verifying identity documents to learn how to redirect users to Stripe. + * @property null|string $verification_flow The configuration token of a Verification Flow from the dashboard. + * @property null|\EDD\Vendor\Stripe\StripeObject $verified_outputs The user’s verified data. + */ +class VerificationSession extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'identity.verification_session'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const STATUS_CANCELED = 'canceled'; + const STATUS_PROCESSING = 'processing'; + const STATUS_REQUIRES_INPUT = 'requires_input'; + const STATUS_VERIFIED = 'verified'; + + const TYPE_DOCUMENT = 'document'; + const TYPE_ID_NUMBER = 'id_number'; + const TYPE_VERIFICATION_FLOW = 'verification_flow'; + + /** + * Creates a VerificationSession object. + * + * After the VerificationSession is created, display a verification modal using the + * session client_secret or send your users to the session’s + * url. + * + * If your API key is in test mode, verification checks won’t actually process, + * though everything else will occur as if in live mode. + * + * Related guide: Verify your + * users’ identity documents + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of VerificationSessions. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Identity\VerificationSession> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a VerificationSession that was previously created. + * + * When the session status is requires_input, you can use this method + * to retrieve a valid client_secret or url to allow + * re-submission. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a VerificationSession object. + * + * When the session status is requires_input, you can use this method + * to update the verification check and options. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession the canceled verification session + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession the redacted verification session + */ + public function redact($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/redact'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Invoice.php b/libraries/Stripe/lib/Invoice.php new file mode 100644 index 00000000000..9275676c304 --- /dev/null +++ b/libraries/Stripe/lib/Invoice.php @@ -0,0 +1,495 @@ +invoice items, and proration adjustments + * that may be caused by subscription upgrades/downgrades (if necessary). + * + * If your invoice is configured to be billed through automatic charges, + * EDD\Vendor\Stripe automatically finalizes your invoice and attempts payment. Note + * that finalizing the invoice, + * when automatic, does + * not happen immediately as the invoice is created. EDD\Vendor\Stripe waits + * until one hour after the last webhook was successfully sent (or the last + * webhook timed out after failing). If you (and the platforms you may have + * connected to) have no webhooks configured, EDD\Vendor\Stripe waits one hour after + * creation to finalize the invoice. + * + * If your invoice is configured to be billed by sending an email, then based on your + * email settings, + * EDD\Vendor\Stripe will email the invoice to your customer and await payment. These + * emails can contain a link to a hosted page to pay the invoice. + * + * EDD\Vendor\Stripe applies any customer credit on the account before determining the + * amount due for the invoice (i.e., the amount that will be actually + * charged). If the amount due for the invoice is less than Stripe's minimum allowed charge + * per currency, the + * invoice is automatically marked paid, and we add the amount due to the + * customer's credit balance which is applied to the next invoice. + * + * More details on the customer's credit balance are + * here. + * + * Related guide: Send invoices to customers + * + * @property null|string $id Unique identifier for the object. This property is always present unless the invoice is an upcoming invoice. See Retrieve an upcoming invoice for more details. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $account_country The country of the business associated with this invoice, most often the business creating the invoice. + * @property null|string $account_name The public name of the business associated with this invoice, most often the business creating the invoice. + * @property null|(string|\EDD\Vendor\Stripe\TaxId)[] $account_tax_ids The account tax IDs associated with the invoice. Only editable when the invoice is a draft. + * @property int $amount_due Final amount due at this time for this invoice. If the invoice's total is smaller than the minimum charge amount, for example, or if there is account credit that can be applied to the invoice, the amount_due may be 0. If there is a positive starting_balance for the invoice (the customer owes money), the amount_due will also take that into account. The charge that gets generated for the invoice will be for the amount specified in amount_due. + * @property int $amount_paid The amount, in cents (or local equivalent), that was paid. + * @property int $amount_remaining The difference between amount_due and amount_paid, in cents (or local equivalent). + * @property int $amount_shipping This is the sum of all the shipping amounts. + * @property null|string|\EDD\Vendor\Stripe\Application $application ID of the Connect Application that created the invoice. + * @property null|int $application_fee_amount The fee in cents (or local equivalent) that will be applied to the invoice and transferred to the application owner's EDD\Vendor\Stripe account when the invoice is paid. + * @property int $attempt_count Number of payment attempts made for this invoice, from the perspective of the payment retry schedule. Any payment attempt counts as the first attempt, and subsequently only automatic retries increment the attempt count. In other words, manual payment attempts after the first attempt do not affect the retry schedule. If a failure is returned with a non-retryable return code, the invoice can no longer be retried unless a new payment method is obtained. Retries will continue to be scheduled, and attempt_count will continue to increment, but retries will only be executed if a new payment method is obtained. + * @property bool $attempted Whether an attempt has been made to pay the invoice. An invoice is not attempted until 1 hour after the invoice.created webhook, for example, so you might not want to display that invoice as unpaid to your users. + * @property null|bool $auto_advance Controls whether EDD\Vendor\Stripe performs automatic collection of the invoice. If false, the invoice's state doesn't automatically advance without an explicit action. + * @property \EDD\Vendor\Stripe\StripeObject $automatic_tax + * @property null|int $automatically_finalizes_at The time when this invoice is currently scheduled to be automatically finalized. The field will be null if the invoice is not scheduled to finalize in the future. If the invoice is not in the draft state, this field will always be null - see finalized_at for the time when an already-finalized invoice was finalized. + * @property null|string $billing_reason

    Indicates the reason why the invoice was created.

    * manual: Unrelated to a subscription, for example, created via the invoice editor. * subscription: No longer in use. Applies to subscriptions from before May 2018 where no distinction was made between updates, cycles, and thresholds. * subscription_create: A new subscription was created. * subscription_cycle: A subscription advanced into a new period. * subscription_threshold: A subscription reached a billing threshold. * subscription_update: A subscription was updated. * upcoming: Reserved for simulated invoices, per the upcoming invoice endpoint.

    + * @property null|string|\EDD\Vendor\Stripe\Charge $charge ID of the latest charge generated for this invoice, if any. + * @property string $collection_method Either charge_automatically, or send_invoice. When charging automatically, EDD\Vendor\Stripe will attempt to pay this invoice using the default source attached to the customer. When sending an invoice, EDD\Vendor\Stripe will email this invoice to the customer with payment instructions. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $custom_fields Custom fields displayed on the invoice. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The ID of the customer who will be billed. + * @property null|\EDD\Vendor\Stripe\StripeObject $customer_address The customer's address. Until the invoice is finalized, this field will equal customer.address. Once the invoice is finalized, this field will no longer be updated. + * @property null|string $customer_email The customer's email. Until the invoice is finalized, this field will equal customer.email. Once the invoice is finalized, this field will no longer be updated. + * @property null|string $customer_name The customer's name. Until the invoice is finalized, this field will equal customer.name. Once the invoice is finalized, this field will no longer be updated. + * @property null|string $customer_phone The customer's phone number. Until the invoice is finalized, this field will equal customer.phone. Once the invoice is finalized, this field will no longer be updated. + * @property null|\EDD\Vendor\Stripe\StripeObject $customer_shipping The customer's shipping information. Until the invoice is finalized, this field will equal customer.shipping. Once the invoice is finalized, this field will no longer be updated. + * @property null|string $customer_tax_exempt The customer's tax exempt status. Until the invoice is finalized, this field will equal customer.tax_exempt. Once the invoice is finalized, this field will no longer be updated. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $customer_tax_ids The customer's tax IDs. Until the invoice is finalized, this field will contain the same tax IDs as customer.tax_ids. Once the invoice is finalized, this field will no longer be updated. + * @property null|string|\EDD\Vendor\Stripe\PaymentMethod $default_payment_method ID of the default payment method for the invoice. It must belong to the customer associated with the invoice. If not set, defaults to the subscription's default payment method, if any, or to the default payment method in the customer's invoice settings. + * @property null|string|\EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source $default_source ID of the default payment source for the invoice. It must belong to the customer associated with the invoice and be in a chargeable state. If not set, defaults to the subscription's default source, if any, or to the customer's default source. + * @property \EDD\Vendor\Stripe\TaxRate[] $default_tax_rates The tax rates applied to this invoice, if any. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. Referenced as 'memo' in the Dashboard. + * @property null|\EDD\Vendor\Stripe\Discount $discount Describes the current discount applied to this invoice, if there is one. Not populated if there are multiple discounts. + * @property (string|\EDD\Vendor\Stripe\Discount)[] $discounts The discounts applied to the invoice. Line item discounts are applied before invoice discounts. Use expand[]=discounts to expand each discount. + * @property null|int $due_date The date on which payment for this invoice is due. This value will be null for invoices where collection_method=charge_automatically. + * @property null|int $effective_at The date when this invoice is in effect. Same as finalized_at unless overwritten. When defined, this value replaces the system-generated 'Date of issue' printed on the invoice PDF and receipt. + * @property null|int $ending_balance Ending customer balance after the invoice is finalized. Invoices are finalized approximately an hour after successful webhook delivery or when payment collection is attempted for the invoice. If the invoice has not been finalized yet, this will be null. + * @property null|string $footer Footer displayed on the invoice. + * @property null|\EDD\Vendor\Stripe\StripeObject $from_invoice Details of the invoice that was cloned. See the revision documentation for more details. + * @property null|string $hosted_invoice_url The URL for the hosted invoice page, which allows customers to view and pay an invoice. If the invoice has not been finalized yet, this will be null. + * @property null|string $invoice_pdf The link to download the PDF for the invoice. If the invoice has not been finalized yet, this will be null. + * @property \EDD\Vendor\Stripe\StripeObject $issuer + * @property null|\EDD\Vendor\Stripe\StripeObject $last_finalization_error The error encountered during the previous attempt to finalize the invoice. This field is cleared when the invoice is successfully finalized. + * @property null|string|\EDD\Vendor\Stripe\Invoice $latest_revision The ID of the most recent non-draft revision of this invoice + * @property \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\InvoiceLineItem> $lines The individual line items that make up the invoice. lines is sorted as follows: (1) pending invoice items (including prorations) in reverse chronological order, (2) subscription items in reverse chronological order, and (3) invoice items added after invoice creation in chronological order. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|int $next_payment_attempt The time at which payment will next be attempted. This value will be null for invoices where collection_method=send_invoice. + * @property null|string $number A unique, identifying string that appears on emails sent to the customer for this invoice. This starts with the customer's unique invoice_prefix if it is specified. + * @property null|string|\EDD\Vendor\Stripe\Account $on_behalf_of The account (if any) for which the funds of the invoice payment are intended. If set, the invoice will be presented with the branding and support information of the specified account. See the Invoices with Connect documentation for details. + * @property bool $paid Whether payment was successfully collected for this invoice. An invoice can be paid (most commonly) with a charge or with credit from the customer's account balance. + * @property bool $paid_out_of_band Returns true if the invoice was manually marked paid, returns false if the invoice hasn't been paid yet or was paid on Stripe. + * @property null|string|\EDD\Vendor\Stripe\PaymentIntent $payment_intent The PaymentIntent associated with this invoice. The PaymentIntent is generated when the invoice is finalized, and can then be used to pay the invoice. Note that voiding an invoice will cancel the PaymentIntent. + * @property \EDD\Vendor\Stripe\StripeObject $payment_settings + * @property int $period_end End of the usage period during which invoice items were added to this invoice. This looks back one period for a subscription invoice. Use the line item period to get the service period for each price. + * @property int $period_start Start of the usage period during which invoice items were added to this invoice. This looks back one period for a subscription invoice. Use the line item period to get the service period for each price. + * @property int $post_payment_credit_notes_amount Total amount of all post-payment credit notes issued for this invoice. + * @property int $pre_payment_credit_notes_amount Total amount of all pre-payment credit notes issued for this invoice. + * @property null|string|\EDD\Vendor\Stripe\Quote $quote The quote this invoice was generated from. + * @property null|string $receipt_number This is the transaction number that appears on email receipts sent for this invoice. + * @property null|\EDD\Vendor\Stripe\StripeObject $rendering The rendering-related settings that control how the invoice is displayed on customer-facing surfaces such as PDF and Hosted Invoice Page. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_cost The details of the cost of shipping, including the ShippingRate applied on the invoice. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_details Shipping details for the invoice. The Invoice PDF will use the shipping_details value if it is set, otherwise the PDF will render the shipping address from the customer. + * @property int $starting_balance Starting customer balance before the invoice is finalized. If the invoice has not been finalized yet, this will be the current customer balance. For revision invoices, this also includes any customer balance that was applied to the original invoice. + * @property null|string $statement_descriptor Extra information about an invoice for the customer's credit card statement. + * @property null|string $status The status of the invoice, one of draft, open, paid, uncollectible, or void. Learn more + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property null|string|\EDD\Vendor\Stripe\Subscription $subscription The subscription that this invoice was prepared for, if any. + * @property null|\EDD\Vendor\Stripe\StripeObject $subscription_details Details about the subscription that created this invoice. + * @property null|int $subscription_proration_date Only set for upcoming invoices that preview prorations. The time used to calculate prorations. + * @property int $subtotal Total of all subscriptions, invoice items, and prorations on the invoice before any invoice level discount or exclusive tax is applied. Item discounts are already incorporated + * @property null|int $subtotal_excluding_tax The integer amount in cents (or local equivalent) representing the subtotal of the invoice before any invoice level discount or tax is applied. Item discounts are already incorporated + * @property null|int $tax The amount of tax on this invoice. This is the sum of all the tax amounts on this invoice. + * @property null|string|\EDD\Vendor\Stripe\TestHelpers\TestClock $test_clock ID of the test clock this invoice belongs to. + * @property null|\EDD\Vendor\Stripe\StripeObject $threshold_reason + * @property int $total Total after discounts and taxes. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $total_discount_amounts The aggregate amounts calculated per discount across all line items. + * @property null|int $total_excluding_tax The integer amount in cents (or local equivalent) representing the total amount of the invoice including all discounts but excluding all tax. + * @property \EDD\Vendor\Stripe\StripeObject[] $total_tax_amounts The aggregate amounts calculated per tax rate for all line items. + * @property null|\EDD\Vendor\Stripe\StripeObject $transfer_data The account (if any) the payment will be attributed to for tax reporting, and where funds from the payment will be transferred to for the invoice. + * @property null|int $webhooks_delivered_at Invoices are automatically paid or sent 1 hour after webhooks are delivered, or until all webhook delivery attempts have been exhausted. This field tracks the time when webhooks for this invoice were successfully delivered. If the invoice had no webhooks to deliver, this will be set while the invoice is being created. + */ +class Invoice extends ApiResource +{ + const OBJECT_NAME = 'invoice'; + + use ApiOperations\NestedResource; + use ApiOperations\Update; + + const BILLING_REASON_AUTOMATIC_PENDING_INVOICE_ITEM_INVOICE = 'automatic_pending_invoice_item_invoice'; + const BILLING_REASON_MANUAL = 'manual'; + const BILLING_REASON_QUOTE_ACCEPT = 'quote_accept'; + const BILLING_REASON_SUBSCRIPTION = 'subscription'; + const BILLING_REASON_SUBSCRIPTION_CREATE = 'subscription_create'; + const BILLING_REASON_SUBSCRIPTION_CYCLE = 'subscription_cycle'; + const BILLING_REASON_SUBSCRIPTION_THRESHOLD = 'subscription_threshold'; + const BILLING_REASON_SUBSCRIPTION_UPDATE = 'subscription_update'; + const BILLING_REASON_UPCOMING = 'upcoming'; + + const COLLECTION_METHOD_CHARGE_AUTOMATICALLY = 'charge_automatically'; + const COLLECTION_METHOD_SEND_INVOICE = 'send_invoice'; + + const CUSTOMER_TAX_EXEMPT_EXEMPT = 'exempt'; + const CUSTOMER_TAX_EXEMPT_NONE = 'none'; + const CUSTOMER_TAX_EXEMPT_REVERSE = 'reverse'; + + const STATUS_DRAFT = 'draft'; + const STATUS_OPEN = 'open'; + const STATUS_PAID = 'paid'; + const STATUS_UNCOLLECTIBLE = 'uncollectible'; + const STATUS_VOID = 'void'; + + /** + * This endpoint creates a draft invoice for a given customer. The invoice remains + * a draft until you finalize the invoice, which + * allows you to pay or send + * the invoice to your customers. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Permanently deletes a one-off invoice draft. This cannot be undone. Attempts to + * delete invoices that are no longer in a draft state will fail; once an invoice + * has been finalized or if an invoice is for a subscription, it must be voided. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * You can list all invoices, or list the invoices for a specific customer. The + * invoices are returned sorted by creation date, with the most recently created + * invoices appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Invoice> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the invoice with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Draft invoices are fully editable. Once an invoice is finalized, monetary values, + * as well as collection_method, become uneditable. + * + * If you would like to stop the EDD\Vendor\Stripe Billing engine from automatically + * finalizing, reattempting payments on, sending reminders for, or automatically reconciling + * invoices, pass auto_advance=false. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + const BILLING_CHARGE_AUTOMATICALLY = 'charge_automatically'; + const BILLING_SEND_INVOICE = 'send_invoice'; + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the added invoice + */ + public function addLines($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/add_lines'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the created invoice + */ + public static function createPreview($params = null, $opts = null) + { + $url = static::classUrl() . '/create_preview'; + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the finalized invoice + */ + public function finalizeInvoice($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/finalize'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the uncollectible invoice + */ + public function markUncollectible($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/mark_uncollectible'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the paid invoice + */ + public function pay($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/pay'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the removed invoice + */ + public function removeLines($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/remove_lines'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the sent invoice + */ + public function sendInvoice($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/send'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the upcoming invoice + */ + public static function upcoming($params = null, $opts = null) + { + $url = static::classUrl() . '/upcoming'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\InvoiceLineItem> list of invoice line items + */ + public static function upcomingLines($params = null, $opts = null) + { + $url = static::classUrl() . '/upcoming/lines'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the updated invoice + */ + public function updateLines($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/update_lines'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice the voided invoice + */ + public function voidInvoice($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/void'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Invoice> the invoice search results + */ + public static function search($params = null, $opts = null) + { + $url = '/v1/invoices/search'; + + return static::_requestPage($url, \EDD\Vendor\Stripe\SearchResult::class, $params, $opts); + } + + const PATH_LINES = '/lines'; + + /** + * @param string $id the ID of the invoice on which to retrieve the invoice line items + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\InvoiceLineItem> the list of invoice line items + */ + public static function allLines($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_LINES, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/InvoiceItem.php b/libraries/Stripe/lib/InvoiceItem.php new file mode 100644 index 00000000000..84b45452ab3 --- /dev/null +++ b/libraries/Stripe/lib/InvoiceItem.php @@ -0,0 +1,158 @@ +invoice. An invoice item is added to an + * invoice by creating or updating it with an invoice field, at which point it will be included as + * an invoice line item within + * invoice.lines. + * + * Invoice Items can be created before you are ready to actually send the invoice. This can be particularly useful when combined + * with a subscription. Sometimes you want to add a charge or credit to a customer, but actually charge + * or credit the customer’s card only at the end of a regular billing cycle. This is useful for combining several charges + * (to minimize per-transaction fees), or for having EDD\Vendor\Stripe tabulate your usage-based billing totals. + * + * Related guides: Integrate with the Invoicing API, Subscription Invoices. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in the currency specified) of the invoice item. This should always be equal to unit_amount * quantity. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string|\EDD\Vendor\Stripe\Customer $customer The ID of the customer who will be billed when this invoice item is billed. + * @property int $date Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property bool $discountable If true, discounts will apply to this invoice item. Always false for prorations. + * @property null|(string|\EDD\Vendor\Stripe\Discount)[] $discounts The discounts which apply to the invoice item. Item discounts are applied before invoice discounts. Use expand[]=discounts to expand each discount. + * @property null|string|\EDD\Vendor\Stripe\Invoice $invoice The ID of the invoice this invoice item belongs to. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property \EDD\Vendor\Stripe\StripeObject $period + * @property null|\EDD\Vendor\Stripe\Plan $plan If the invoice item is a proration, the plan of the subscription that the proration was computed for. + * @property null|\EDD\Vendor\Stripe\Price $price The price of the invoice item. + * @property bool $proration Whether the invoice item was created automatically as a proration adjustment when the customer switched plans. + * @property int $quantity Quantity of units for the invoice item. If the invoice item is a proration, the quantity of the subscription that the proration was computed for. + * @property null|string|\EDD\Vendor\Stripe\Subscription $subscription The subscription that this invoice item has been created for, if any. + * @property null|string $subscription_item The subscription item that this invoice item has been created for, if any. + * @property null|\EDD\Vendor\Stripe\TaxRate[] $tax_rates The tax rates which apply to the invoice item. When set, the default_tax_rates on the invoice do not apply to this invoice item. + * @property null|string|\EDD\Vendor\Stripe\TestHelpers\TestClock $test_clock ID of the test clock this invoice item belongs to. + * @property null|int $unit_amount Unit amount (in the currency specified) of the invoice item. + * @property null|string $unit_amount_decimal Same as unit_amount, but contains a decimal value with at most 12 decimal places. + */ +class InvoiceItem extends ApiResource +{ + const OBJECT_NAME = 'invoiceitem'; + + use ApiOperations\Update; + + /** + * Creates an item to be added to a draft invoice (up to 250 items per invoice). If + * no invoice is specified, the item will be on the next invoice created for the + * customer specified. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceItem the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes an invoice item, removing it from an invoice. Deleting invoice items is + * only possible when they’re not attached to invoices, or if it’s attached to a + * draft invoice. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceItem the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of your invoice items. Invoice items are returned sorted by + * creation date, with the most recently created invoice items appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\InvoiceItem> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the invoice item with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceItem + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the amount or description of an invoice item on an upcoming invoice. + * Updating an invoice item is only possible before the invoice it’s attached to is + * closed. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceItem the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/InvoiceLineItem.php b/libraries/Stripe/lib/InvoiceLineItem.php new file mode 100644 index 00000000000..a4c4f000c2b --- /dev/null +++ b/libraries/Stripe/lib/InvoiceLineItem.php @@ -0,0 +1,71 @@ +invoice and only exist within the context of an invoice. + * + * Each line item is backed by either an invoice item or a subscription item. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount The amount, in cents (or local equivalent). + * @property null|int $amount_excluding_tax The integer amount in cents (or local equivalent) representing the amount for this line item, excluding all tax and discounts. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $discount_amounts The amount of discount calculated per discount for this line item. + * @property bool $discountable If true, discounts will apply to this line item. Always false for prorations. + * @property (string|\EDD\Vendor\Stripe\Discount)[] $discounts The discounts applied to the invoice line item. Line item discounts are applied before invoice discounts. Use expand[]=discounts to expand each discount. + * @property null|string $invoice The ID of the invoice that contains this line item. + * @property null|string|\EDD\Vendor\Stripe\InvoiceItem $invoice_item The ID of the invoice item associated with this line item if any. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. Note that for line items with type=subscription, metadata reflects the current metadata from the subscription associated with the line item, unless the invoice line was directly updated with different metadata after creation. + * @property \EDD\Vendor\Stripe\StripeObject $period + * @property null|\EDD\Vendor\Stripe\Plan $plan The plan of the subscription, if the line item is a subscription or a proration. + * @property null|\EDD\Vendor\Stripe\Price $price The price of the line item. + * @property bool $proration Whether this is a proration. + * @property null|\EDD\Vendor\Stripe\StripeObject $proration_details Additional details for proration line items + * @property null|int $quantity The quantity of the subscription, if the line item is a subscription or a proration. + * @property null|string|\EDD\Vendor\Stripe\Subscription $subscription The subscription that the invoice item pertains to, if any. + * @property null|string|\EDD\Vendor\Stripe\SubscriptionItem $subscription_item The subscription item that generated this line item. Left empty if the line item is not an explicit result of a subscription. + * @property \EDD\Vendor\Stripe\StripeObject[] $tax_amounts The amount of tax calculated per tax rate for this line item + * @property \EDD\Vendor\Stripe\TaxRate[] $tax_rates The tax rates which apply to the line item. + * @property string $type A string identifying the type of the source of this line item, either an invoiceitem or a subscription. + * @property null|string $unit_amount_excluding_tax The amount in cents (or local equivalent) representing the unit amount for this line item, excluding all tax and discounts. + */ +class InvoiceLineItem extends ApiResource +{ + const OBJECT_NAME = 'line_item'; + + use ApiOperations\Update; + + /** + * Updates an invoice’s line item. Some fields, such as tax_amounts, + * only live on the invoice line item, so they can only be updated through this + * endpoint. Other fields, such as amount, live on both the invoice + * item and the invoice line item, so updates on this endpoint will propagate to + * the invoice item as well. Updating an invoice’s line item is only possible + * before the invoice is finalized. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceLineItem the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/InvoiceRenderingTemplate.php b/libraries/Stripe/lib/InvoiceRenderingTemplate.php new file mode 100644 index 00000000000..7208fe52a9d --- /dev/null +++ b/libraries/Stripe/lib/InvoiceRenderingTemplate.php @@ -0,0 +1,99 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $nickname A brief description of the template, hidden from customers + * @property string $status The status of the template, one of active or archived. + * @property int $version Version of this template; version increases by one when an update on the template changes any field that controls invoice rendering + */ +class InvoiceRenderingTemplate extends ApiResource +{ + const OBJECT_NAME = 'invoice_rendering_template'; + + const STATUS_ACTIVE = 'active'; + const STATUS_ARCHIVED = 'archived'; + + /** + * List all templates, ordered by creation date, with the most recently created + * template appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\InvoiceRenderingTemplate> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an invoice rendering template with the given ID. It by default returns + * the latest version of the template. Optionally, specify a version to see + * previous versions. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceRenderingTemplate + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceRenderingTemplate the archived invoice rendering template + */ + public function archive($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/archive'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceRenderingTemplate the unarchived invoice rendering template + */ + public function unarchive($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/unarchive'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Issuing/Authorization.php b/libraries/Stripe/lib/Issuing/Authorization.php new file mode 100644 index 00000000000..d873088023e --- /dev/null +++ b/libraries/Stripe/lib/Issuing/Authorization.php @@ -0,0 +1,154 @@ +issued card is used to make a purchase, an Issuing Authorization + * object is created. Authorizations must be approved for the + * purchase to be completed successfully. + * + * Related guide: Issued card authorizations + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount The total amount that was authorized or rejected. This amount is in currency and in the smallest currency unit. amount should be the same as merchant_amount, unless currency and merchant_currency are different. + * @property null|\EDD\Vendor\Stripe\StripeObject $amount_details Detailed breakdown of amount components. These amounts are denominated in currency and in the smallest currency unit. + * @property bool $approved Whether the authorization has been approved. + * @property string $authorization_method How the card details were provided. + * @property \EDD\Vendor\Stripe\BalanceTransaction[] $balance_transactions List of balance transactions associated with this authorization. + * @property \EDD\Vendor\Stripe\Issuing\Card $card You can create physical or virtual cards that are issued to cardholders. + * @property null|string|\EDD\Vendor\Stripe\Issuing\Cardholder $cardholder The cardholder to whom this authorization belongs. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency The currency of the cardholder. This currency can be different from the currency presented at authorization and the merchant_currency field on this authorization. Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|\EDD\Vendor\Stripe\StripeObject $fleet Fleet-specific information for authorizations using Fleet cards. + * @property null|\EDD\Vendor\Stripe\StripeObject $fuel Information about fuel that was purchased with this transaction. Typically this information is received from the merchant after the authorization has been approved and the fuel dispensed. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property int $merchant_amount The total amount that was authorized or rejected. This amount is in the merchant_currency and in the smallest currency unit. merchant_amount should be the same as amount, unless merchant_currency and currency are different. + * @property string $merchant_currency The local currency that was presented to the cardholder for the authorization. This currency can be different from the cardholder currency and the currency field on this authorization. Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property \EDD\Vendor\Stripe\StripeObject $merchant_data + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $network_data Details about the authorization, such as identifiers, set by the card network. + * @property null|\EDD\Vendor\Stripe\StripeObject $pending_request The pending authorization request. This field will only be non-null during an issuing_authorization.request webhook. + * @property \EDD\Vendor\Stripe\StripeObject[] $request_history History of every time a pending_request authorization was approved/declined, either by you directly or by EDD\Vendor\Stripe (e.g. based on your spending_controls). If the merchant changes the authorization by performing an incremental authorization, you can look at this field to see the previous requests for the authorization. This field can be helpful in determining why a given authorization was approved/declined. + * @property string $status The current status of the authorization in its lifecycle. + * @property null|string|\EDD\Vendor\Stripe\Issuing\Token $token Token object used for this authorization. If a network token was not used for this authorization, this field will be null. + * @property \EDD\Vendor\Stripe\Issuing\Transaction[] $transactions List of transactions associated with this authorization. + * @property null|\EDD\Vendor\Stripe\StripeObject $treasury Treasury details related to this authorization if it was created on a FinancialAccount. + * @property \EDD\Vendor\Stripe\StripeObject $verification_data + * @property null|string $wallet The digital wallet used for this transaction. One of apple_pay, google_pay, or samsung_pay. Will populate as null when no digital wallet was utilized. + */ +class Authorization extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'issuing.authorization'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const AUTHORIZATION_METHOD_CHIP = 'chip'; + const AUTHORIZATION_METHOD_CONTACTLESS = 'contactless'; + const AUTHORIZATION_METHOD_KEYED_IN = 'keyed_in'; + const AUTHORIZATION_METHOD_ONLINE = 'online'; + const AUTHORIZATION_METHOD_SWIPE = 'swipe'; + + const STATUS_CLOSED = 'closed'; + const STATUS_PENDING = 'pending'; + const STATUS_REVERSED = 'reversed'; + + /** + * Returns a list of Issuing Authorization objects. The objects are + * sorted in descending order by creation date, with the most recently created + * object appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Authorization> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an Issuing Authorization object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified Issuing Authorization object by setting the + * values of the parameters passed. Any parameters not provided will be left + * unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization the approved authorization + */ + public function approve($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/approve'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization the declined authorization + */ + public function decline($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/decline'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Issuing/Card.php b/libraries/Stripe/lib/Issuing/Card.php new file mode 100644 index 00000000000..60d39ad0064 --- /dev/null +++ b/libraries/Stripe/lib/Issuing/Card.php @@ -0,0 +1,140 @@ +create physical or virtual cards that are issued to cardholders. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string $brand The brand of the card. + * @property null|string $cancellation_reason The reason why the card was canceled. + * @property \EDD\Vendor\Stripe\Issuing\Cardholder $cardholder

    An Issuing Cardholder object represents an individual or business entity who is issued cards.

    Related guide: How to create a cardholder

    + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Supported currencies are usd in the US, eur in the EU, and gbp in the UK. + * @property null|string $cvc The card's CVC. For security reasons, this is only available for virtual cards, and will be omitted unless you explicitly request it with the expand parameter. Additionally, it's only available via the "Retrieve a card" endpoint, not via "List all cards" or any other endpoint. + * @property int $exp_month The expiration month of the card. + * @property int $exp_year The expiration year of the card. + * @property null|string $financial_account The financial account this card is attached to. + * @property string $last4 The last 4 digits of the card number. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $number The full unredacted card number. For security reasons, this is only available for virtual cards, and will be omitted unless you explicitly request it with the expand parameter. Additionally, it's only available via the "Retrieve a card" endpoint, not via "List all cards" or any other endpoint. + * @property null|string|\EDD\Vendor\Stripe\Issuing\PersonalizationDesign $personalization_design The personalization design object belonging to this card. + * @property null|string|\EDD\Vendor\Stripe\Issuing\Card $replaced_by The latest card that replaces this card, if any. + * @property null|string|\EDD\Vendor\Stripe\Issuing\Card $replacement_for The card this card replaces, if any. + * @property null|string $replacement_reason The reason why the previous card needed to be replaced. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping Where and how the card will be shipped. + * @property \EDD\Vendor\Stripe\StripeObject $spending_controls + * @property string $status Whether authorizations can be approved on this card. May be blocked from activating cards depending on past-due Cardholder requirements. Defaults to inactive. + * @property string $type The type of the card. + * @property null|\EDD\Vendor\Stripe\StripeObject $wallets Information relating to digital wallets (like Apple Pay and Google Pay). + */ +class Card extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'issuing.card'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const CANCELLATION_REASON_DESIGN_REJECTED = 'design_rejected'; + const CANCELLATION_REASON_LOST = 'lost'; + const CANCELLATION_REASON_STOLEN = 'stolen'; + + const REPLACEMENT_REASON_DAMAGED = 'damaged'; + const REPLACEMENT_REASON_EXPIRED = 'expired'; + const REPLACEMENT_REASON_LOST = 'lost'; + const REPLACEMENT_REASON_STOLEN = 'stolen'; + + const STATUS_ACTIVE = 'active'; + const STATUS_CANCELED = 'canceled'; + const STATUS_INACTIVE = 'inactive'; + + const TYPE_PHYSICAL = 'physical'; + const TYPE_VIRTUAL = 'virtual'; + + /** + * Creates an Issuing Card object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of Issuing Card objects. The objects are sorted in + * descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Card> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an Issuing Card object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified Issuing Card object by setting the values of + * the parameters passed. Any parameters not provided will be left unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Issuing/CardDetails.php b/libraries/Stripe/lib/Issuing/CardDetails.php new file mode 100644 index 00000000000..6eaa61ecb44 --- /dev/null +++ b/libraries/Stripe/lib/Issuing/CardDetails.php @@ -0,0 +1,19 @@ +Cardholder object represents an individual or business entity who is issued cards. + * + * Related guide: How to create a cardholder + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property \EDD\Vendor\Stripe\StripeObject $billing + * @property null|\EDD\Vendor\Stripe\StripeObject $company Additional information about a company cardholder. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $email The cardholder's email address. + * @property null|\EDD\Vendor\Stripe\StripeObject $individual Additional information about an individual cardholder. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $name The cardholder's name. This will be printed on cards issued to them. + * @property null|string $phone_number The cardholder's phone number. This is required for all cardholders who will be creating EU cards. See the 3D Secure documentation for more details. + * @property null|string[] $preferred_locales The cardholder’s preferred locales (languages), ordered by preference. Locales can be de, en, es, fr, or it. This changes the language of the 3D Secure flow and one-time password messages sent to the cardholder. + * @property \EDD\Vendor\Stripe\StripeObject $requirements + * @property null|\EDD\Vendor\Stripe\StripeObject $spending_controls Rules that control spending across this cardholder's cards. Refer to our documentation for more details. + * @property string $status Specifies whether to permit authorizations on this cardholder's cards. + * @property string $type One of individual or company. See Choose a cardholder type for more details. + */ +class Cardholder extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'issuing.cardholder'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const STATUS_ACTIVE = 'active'; + const STATUS_BLOCKED = 'blocked'; + const STATUS_INACTIVE = 'inactive'; + + const TYPE_COMPANY = 'company'; + const TYPE_INDIVIDUAL = 'individual'; + + /** + * Creates a new Issuing Cardholder object that can be issued cards. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Cardholder the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of Issuing Cardholder objects. The objects are + * sorted in descending order by creation date, with the most recently created + * object appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Cardholder> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an Issuing Cardholder object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Cardholder + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified Issuing Cardholder object by setting the + * values of the parameters passed. Any parameters not provided will be left + * unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Cardholder the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Issuing/Dispute.php b/libraries/Stripe/lib/Issuing/Dispute.php new file mode 100644 index 00000000000..186c0a0aa7a --- /dev/null +++ b/libraries/Stripe/lib/Issuing/Dispute.php @@ -0,0 +1,165 @@ +card issuer, you can dispute transactions that the cardholder does not recognize, suspects to be fraudulent, or has other issues with. + * + * Related guide: Issuing disputes + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Disputed amount in the card's currency and in the smallest currency unit. Usually the amount of the transaction, but can differ (usually because of currency fluctuation). + * @property null|\EDD\Vendor\Stripe\BalanceTransaction[] $balance_transactions List of balance transactions associated with the dispute. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency The currency the transaction was made in. + * @property \EDD\Vendor\Stripe\StripeObject $evidence + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $loss_reason The enum that describes the dispute loss outcome. If the dispute is not lost, this field will be absent. New enum values may be added in the future, so be sure to handle unknown values. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $status Current status of the dispute. + * @property string|\EDD\Vendor\Stripe\Issuing\Transaction $transaction The transaction being disputed. + * @property null|\EDD\Vendor\Stripe\StripeObject $treasury Treasury details related to this dispute if it was created on a [FinancialAccount](/docs/api/treasury/financial_accounts + */ +class Dispute extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'issuing.dispute'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const LOSS_REASON_CARDHOLDER_AUTHENTICATION_ISSUER_LIABILITY = 'cardholder_authentication_issuer_liability'; + const LOSS_REASON_ECI5_TOKEN_TRANSACTION_WITH_TAVV = 'eci5_token_transaction_with_tavv'; + const LOSS_REASON_EXCESS_DISPUTES_IN_TIMEFRAME = 'excess_disputes_in_timeframe'; + const LOSS_REASON_HAS_NOT_MET_THE_MINIMUM_DISPUTE_AMOUNT_REQUIREMENTS = 'has_not_met_the_minimum_dispute_amount_requirements'; + const LOSS_REASON_INVALID_DUPLICATE_DISPUTE = 'invalid_duplicate_dispute'; + const LOSS_REASON_INVALID_INCORRECT_AMOUNT_DISPUTE = 'invalid_incorrect_amount_dispute'; + const LOSS_REASON_INVALID_NO_AUTHORIZATION = 'invalid_no_authorization'; + const LOSS_REASON_INVALID_USE_OF_DISPUTES = 'invalid_use_of_disputes'; + const LOSS_REASON_MERCHANDISE_DELIVERED_OR_SHIPPED = 'merchandise_delivered_or_shipped'; + const LOSS_REASON_MERCHANDISE_OR_SERVICE_AS_DESCRIBED = 'merchandise_or_service_as_described'; + const LOSS_REASON_NOT_CANCELLED = 'not_cancelled'; + const LOSS_REASON_OTHER = 'other'; + const LOSS_REASON_REFUND_ISSUED = 'refund_issued'; + const LOSS_REASON_SUBMITTED_BEYOND_ALLOWABLE_TIME_LIMIT = 'submitted_beyond_allowable_time_limit'; + const LOSS_REASON_TRANSACTION_3DS_REQUIRED = 'transaction_3ds_required'; + const LOSS_REASON_TRANSACTION_APPROVED_AFTER_PRIOR_FRAUD_DISPUTE = 'transaction_approved_after_prior_fraud_dispute'; + const LOSS_REASON_TRANSACTION_AUTHORIZED = 'transaction_authorized'; + const LOSS_REASON_TRANSACTION_ELECTRONICALLY_READ = 'transaction_electronically_read'; + const LOSS_REASON_TRANSACTION_QUALIFIES_FOR_VISA_EASY_PAYMENT_SERVICE = 'transaction_qualifies_for_visa_easy_payment_service'; + const LOSS_REASON_TRANSACTION_UNATTENDED = 'transaction_unattended'; + + const STATUS_EXPIRED = 'expired'; + const STATUS_LOST = 'lost'; + const STATUS_SUBMITTED = 'submitted'; + const STATUS_UNSUBMITTED = 'unsubmitted'; + const STATUS_WON = 'won'; + + /** + * Creates an Issuing Dispute object. Individual pieces of evidence + * within the evidence object are optional at this point. EDD\Vendor\Stripe only + * validates that required evidence is present during submission. Refer to Dispute + * reasons and evidence for more details about evidence requirements. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Dispute the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of Issuing Dispute objects. The objects are sorted + * in descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Dispute> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an Issuing Dispute object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Dispute + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified Issuing Dispute object by setting the values + * of the parameters passed. Any parameters not provided will be left unchanged. + * Properties on the evidence object can be unset by passing in an + * empty string. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Dispute the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Dispute the submited dispute + */ + public function submit($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/submit'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Issuing/PersonalizationDesign.php b/libraries/Stripe/lib/Issuing/PersonalizationDesign.php new file mode 100644 index 00000000000..579db0835a2 --- /dev/null +++ b/libraries/Stripe/lib/Issuing/PersonalizationDesign.php @@ -0,0 +1,117 @@ +purpose value of issuing_logo. + * @property null|\EDD\Vendor\Stripe\StripeObject $carrier_text Hash containing carrier text, for use with physical bundles that support carrier text. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $lookup_key A lookup key used to retrieve personalization designs dynamically from a static string. This may be up to 200 characters. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $name Friendly display name. + * @property string|\EDD\Vendor\Stripe\Issuing\PhysicalBundle $physical_bundle The physical bundle object belonging to this personalization design. + * @property \EDD\Vendor\Stripe\StripeObject $preferences + * @property \EDD\Vendor\Stripe\StripeObject $rejection_reasons + * @property string $status Whether this personalization design can be used to create cards. + */ +class PersonalizationDesign extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'issuing.personalization_design'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const STATUS_ACTIVE = 'active'; + const STATUS_INACTIVE = 'inactive'; + const STATUS_REJECTED = 'rejected'; + const STATUS_REVIEW = 'review'; + + /** + * Creates a personalization design object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of personalization design objects. The objects are sorted in + * descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\PersonalizationDesign> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a personalization design object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a card personalization object. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Issuing/PhysicalBundle.php b/libraries/Stripe/lib/Issuing/PhysicalBundle.php new file mode 100644 index 00000000000..3fd4393d589 --- /dev/null +++ b/libraries/Stripe/lib/Issuing/PhysicalBundle.php @@ -0,0 +1,65 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $name Friendly display name. + * @property string $status Whether this physical bundle can be used to create cards. + * @property string $type Whether this physical bundle is a standard EDD\Vendor\Stripe offering or custom-made for you. + */ +class PhysicalBundle extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'issuing.physical_bundle'; + + const STATUS_ACTIVE = 'active'; + const STATUS_INACTIVE = 'inactive'; + const STATUS_REVIEW = 'review'; + + const TYPE_CUSTOM = 'custom'; + const TYPE_STANDARD = 'standard'; + + /** + * Returns a list of physical bundle objects. The objects are sorted in descending + * order by creation date, with the most recently created object appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\PhysicalBundle> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a physical bundle object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PhysicalBundle + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Issuing/Token.php b/libraries/Stripe/lib/Issuing/Token.php new file mode 100644 index 00000000000..efb97dd9592 --- /dev/null +++ b/libraries/Stripe/lib/Issuing/Token.php @@ -0,0 +1,100 @@ +card issuer, you can view and manage these tokens through Stripe. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string|\EDD\Vendor\Stripe\Issuing\Card $card Card associated with this token. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $device_fingerprint The hashed ID derived from the device ID from the card network associated with the token. + * @property null|string $last4 The last four digits of the token. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $network The token service provider / card network associated with the token. + * @property null|\EDD\Vendor\Stripe\StripeObject $network_data + * @property int $network_updated_at Time at which the token was last updated by the card network. Measured in seconds since the Unix epoch. + * @property string $status The usage state of the token. + * @property null|string $wallet_provider The digital wallet for this token, if one was used. + */ +class Token extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'issuing.token'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const NETWORK_MASTERCARD = 'mastercard'; + const NETWORK_VISA = 'visa'; + + const STATUS_ACTIVE = 'active'; + const STATUS_DELETED = 'deleted'; + const STATUS_REQUESTED = 'requested'; + const STATUS_SUSPENDED = 'suspended'; + + const WALLET_PROVIDER_APPLE_PAY = 'apple_pay'; + const WALLET_PROVIDER_GOOGLE_PAY = 'google_pay'; + const WALLET_PROVIDER_SAMSUNG_PAY = 'samsung_pay'; + + /** + * Lists all Issuing Token objects for a given card. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Token> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an Issuing Token object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Token + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Attempts to update the specified Issuing Token object to the status + * specified. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Token the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Issuing/Transaction.php b/libraries/Stripe/lib/Issuing/Transaction.php new file mode 100644 index 00000000000..af8d02b86de --- /dev/null +++ b/libraries/Stripe/lib/Issuing/Transaction.php @@ -0,0 +1,112 @@ +issued card that results in funds entering or leaving + * your EDD\Vendor\Stripe account, such as a completed purchase or refund, is represented by an Issuing + * Transaction object. + * + * Related guide: Issued card transactions + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount The transaction amount, which will be reflected in your balance. This amount is in your currency and in the smallest currency unit. + * @property null|\EDD\Vendor\Stripe\StripeObject $amount_details Detailed breakdown of amount components. These amounts are denominated in currency and in the smallest currency unit. + * @property null|string|\EDD\Vendor\Stripe\Issuing\Authorization $authorization The Authorization object that led to this transaction. + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $balance_transaction ID of the balance transaction associated with this transaction. + * @property string|\EDD\Vendor\Stripe\Issuing\Card $card The card used to make this transaction. + * @property null|string|\EDD\Vendor\Stripe\Issuing\Cardholder $cardholder The cardholder to whom this transaction belongs. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string|\EDD\Vendor\Stripe\Issuing\Dispute $dispute If you've disputed the transaction, the ID of the dispute. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property int $merchant_amount The amount that the merchant will receive, denominated in merchant_currency and in the smallest currency unit. It will be different from amount if the merchant is taking payment in a different currency. + * @property string $merchant_currency The currency with which the merchant is taking payment. + * @property \EDD\Vendor\Stripe\StripeObject $merchant_data + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $network_data Details about the transaction, such as processing dates, set by the card network. + * @property null|\EDD\Vendor\Stripe\StripeObject $purchase_details Additional purchase information that is optionally provided by the merchant. + * @property null|string|\EDD\Vendor\Stripe\Issuing\Token $token Token object used for this transaction. If a network token was not used for this transaction, this field will be null. + * @property null|\EDD\Vendor\Stripe\StripeObject $treasury Treasury details related to this transaction if it was created on a [FinancialAccount](/docs/api/treasury/financial_accounts + * @property string $type The nature of the transaction. + * @property null|string $wallet The digital wallet used for this transaction. One of apple_pay, google_pay, or samsung_pay. + */ +class Transaction extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'issuing.transaction'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const TYPE_CAPTURE = 'capture'; + const TYPE_REFUND = 'refund'; + + const WALLET_APPLE_PAY = 'apple_pay'; + const WALLET_GOOGLE_PAY = 'google_pay'; + const WALLET_SAMSUNG_PAY = 'samsung_pay'; + + /** + * Returns a list of Issuing Transaction objects. The objects are + * sorted in descending order by creation date, with the most recently created + * object appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Transaction> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an Issuing Transaction object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Transaction + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified Issuing Transaction object by setting the + * values of the parameters passed. Any parameters not provided will be left + * unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Transaction the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/LineItem.php b/libraries/Stripe/lib/LineItem.php new file mode 100644 index 00000000000..4985672d647 --- /dev/null +++ b/libraries/Stripe/lib/LineItem.php @@ -0,0 +1,26 @@ +ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. Defaults to product name. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $discounts The discounts applied to the line item. + * @property null|\EDD\Vendor\Stripe\Price $price The price used to generate the line item. + * @property null|int $quantity The quantity of products being purchased. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $taxes The taxes applied to the line item. + */ +class LineItem extends ApiResource +{ + const OBJECT_NAME = 'item'; +} diff --git a/libraries/Stripe/lib/LoginLink.php b/libraries/Stripe/lib/LoginLink.php new file mode 100644 index 00000000000..9258983d37a --- /dev/null +++ b/libraries/Stripe/lib/LoginLink.php @@ -0,0 +1,17 @@ +account.controller.stripe_dashboard.type must be express to have access to the Express Dashboard. + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $url The URL for the login link. + */ +class LoginLink extends ApiResource +{ + const OBJECT_NAME = 'login_link'; +} diff --git a/libraries/Stripe/lib/Mandate.php b/libraries/Stripe/lib/Mandate.php new file mode 100644 index 00000000000..a85fe3c46b3 --- /dev/null +++ b/libraries/Stripe/lib/Mandate.php @@ -0,0 +1,51 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $multi_use + * @property null|string $on_behalf_of The account (if any) that the mandate is intended for. + * @property string|\EDD\Vendor\Stripe\PaymentMethod $payment_method ID of the payment method associated with this mandate. + * @property \EDD\Vendor\Stripe\StripeObject $payment_method_details + * @property null|\EDD\Vendor\Stripe\StripeObject $single_use + * @property string $status The mandate status indicates whether or not you can use it to initiate a payment. + * @property string $type The type of the mandate. + */ +class Mandate extends ApiResource +{ + const OBJECT_NAME = 'mandate'; + + const STATUS_ACTIVE = 'active'; + const STATUS_INACTIVE = 'inactive'; + const STATUS_PENDING = 'pending'; + + const TYPE_MULTI_USE = 'multi_use'; + const TYPE_SINGLE_USE = 'single_use'; + + /** + * Retrieves a Mandate object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Mandate + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/OAuth.php b/libraries/Stripe/lib/OAuth.php new file mode 100644 index 00000000000..b545c250363 --- /dev/null +++ b/libraries/Stripe/lib/OAuth.php @@ -0,0 +1,101 @@ +request( + 'post', + '/oauth/token', + $params, + null + ); + + return Util\Util::convertToStripeObject($response->json, $opts); + } + + /** + * Disconnects an account from your platform. + * + * @param null|array $params + * @param null|array $opts + * + * @throws \EDD\Vendor\Stripe\Exception\OAuth\OAuthErrorException if the request fails + * + * @return StripeObject object containing the response from the API + */ + public static function deauthorize($params = null, $opts = null) + { + $params = $params ?: []; + $base = ($opts && \array_key_exists('connect_base', $opts)) ? $opts['connect_base'] : Stripe::$connectBase; + $requestor = new ApiRequestor(null, $base); + $params['client_id'] = self::_getClientId($params); + list($response, $apiKey) = $requestor->request( + 'post', + '/oauth/deauthorize', + $params, + null + ); + + return Util\Util::convertToStripeObject($response->json, $opts); + } + + private static function _getClientId($params = null) + { + $clientId = ($params && \array_key_exists('client_id', $params)) ? $params['client_id'] : null; + if (null === $clientId) { + $clientId = Stripe::getClientId(); + } + if (null === $clientId) { + $msg = 'No client_id provided. (HINT: set your client_id using ' + . '"Stripe::setClientId()". You can find your client_ids ' + . 'in your EDD\Vendor\Stripe dashboard at ' + . 'https://dashboard.stripe.com/account/applications/settings, ' + . 'after registering your account as a platform. See ' + . 'https://stripe.com/docs/connect/standard-accounts for details, ' + . 'or email support@stripe.com if you have any questions.'; + + throw new Exception\AuthenticationException($msg); + } + + return $clientId; + } +} diff --git a/libraries/Stripe/lib/OAuthErrorObject.php b/libraries/Stripe/lib/OAuthErrorObject.php new file mode 100644 index 00000000000..d3a42f9a6ae --- /dev/null +++ b/libraries/Stripe/lib/OAuthErrorObject.php @@ -0,0 +1,31 @@ + null, + 'error_description' => null, + ], $values); + parent::refreshFrom($values, $opts, $partial); + } +} diff --git a/libraries/Stripe/lib/PaymentIntent.php b/libraries/Stripe/lib/PaymentIntent.php new file mode 100644 index 00000000000..65de8d21fcc --- /dev/null +++ b/libraries/Stripe/lib/PaymentIntent.php @@ -0,0 +1,313 @@ +multiple statuses + * throughout its lifetime as it interfaces with Stripe.js to perform + * authentication flows and ultimately creates at most one successful charge. + * + * Related guide: Payment Intents API + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount intended to be collected by this PaymentIntent. A positive integer representing how much to charge in the smallest currency unit (e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal currency). The minimum amount is $0.50 US or equivalent in charge currency. The amount value supports up to eight digits (e.g., a value of 99999999 for a USD charge of $999,999.99). + * @property int $amount_capturable Amount that can be captured from this PaymentIntent. + * @property null|\EDD\Vendor\Stripe\StripeObject $amount_details + * @property int $amount_received Amount that this PaymentIntent collects. + * @property null|string|\EDD\Vendor\Stripe\Application $application ID of the Connect application that created the PaymentIntent. + * @property null|int $application_fee_amount The amount of the application fee (if any) that will be requested to be applied to the payment and transferred to the application owner's EDD\Vendor\Stripe account. The amount of the application fee collected will be capped at the total payment amount. For more information, see the PaymentIntents use case for connected accounts. + * @property null|\EDD\Vendor\Stripe\StripeObject $automatic_payment_methods Settings to configure compatible payment methods from the EDD\Vendor\Stripe Dashboard + * @property null|int $canceled_at Populated when status is canceled, this is the time at which the PaymentIntent was canceled. Measured in seconds since the Unix epoch. + * @property null|string $cancellation_reason Reason for cancellation of this PaymentIntent, either user-provided (duplicate, fraudulent, requested_by_customer, or abandoned) or generated by EDD\Vendor\Stripe internally (failed_invoice, void_invoice, or automatic). + * @property string $capture_method Controls when the funds will be captured from the customer's account. + * @property null|string $client_secret

    The client secret of this PaymentIntent. Used for client-side retrieval using a publishable key.

    The client secret can be used to complete a payment from your frontend. It should not be stored, logged, or exposed to anyone other than the customer. Make sure that you have TLS enabled on any page that includes the client secret.

    Refer to our docs to accept a payment and learn about how client_secret should be handled.

    + * @property string $confirmation_method Describes whether we can confirm this PaymentIntent automatically, or if it requires customer action to confirm the payment. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer

    ID of the Customer this PaymentIntent belongs to, if one exists.

    Payment methods attached to other Customers cannot be used with this PaymentIntent.

    If setup_future_usage is set and this PaymentIntent's payment method is not card_present, then the payment method attaches to the Customer after the PaymentIntent has been confirmed and any required actions from the user are complete. If the payment method is card_present and isn't a digital wallet, then a generated_card payment method representing the card is created and attached to the Customer instead.

    + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|string|\EDD\Vendor\Stripe\Invoice $invoice ID of the invoice that created this PaymentIntent, if it exists. + * @property null|\EDD\Vendor\Stripe\StripeObject $last_payment_error The payment error encountered in the previous PaymentIntent confirmation. It will be cleared if the PaymentIntent is later updated for any reason. + * @property null|string|\EDD\Vendor\Stripe\Charge $latest_charge ID of the latest Charge object created by this PaymentIntent. This property is null until PaymentIntent confirmation is attempted. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. Learn more about storing information in metadata. + * @property null|\EDD\Vendor\Stripe\StripeObject $next_action If present, this property tells you what actions you need to take in order for your customer to fulfill a payment using the provided source. + * @property null|string|\EDD\Vendor\Stripe\Account $on_behalf_of The account (if any) for which the funds of the PaymentIntent are intended. See the PaymentIntents use case for connected accounts for details. + * @property null|string|\EDD\Vendor\Stripe\PaymentMethod $payment_method ID of the payment method used in this PaymentIntent. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_configuration_details Information about the payment method configuration used for this PaymentIntent. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_options Payment-method-specific configuration for this PaymentIntent. + * @property string[] $payment_method_types The list of payment method types (e.g. card) that this PaymentIntent is allowed to use. + * @property null|\EDD\Vendor\Stripe\StripeObject $processing If present, this property tells you about the processing state of the payment. + * @property null|string $receipt_email Email address that the receipt for the resulting payment will be sent to. If receipt_email is specified for a payment in live mode, a receipt will be sent regardless of your email settings. + * @property null|string|\EDD\Vendor\Stripe\Review $review ID of the review associated with this PaymentIntent, if any. + * @property null|string $setup_future_usage

    Indicates that you intend to make future payments with this PaymentIntent's payment method.

    If you provide a Customer with the PaymentIntent, you can use this parameter to attach the payment method to the Customer after the PaymentIntent is confirmed and the customer completes any required actions. If you don't provide a Customer, you can still attach the payment method to a Customer after the transaction completes.

    If the payment method is card_present and isn't a digital wallet, EDD\Vendor\Stripe creates and attaches a generated_card payment method representing the card to the Customer instead.

    When processing card payments, EDD\Vendor\Stripe uses setup_future_usage to help you comply with regional legislation and network rules, such as SCA.

    + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping Shipping information for this PaymentIntent. + * @property null|string|\EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source $source This is a legacy field that will be removed in the future. It is the ID of the Source object that is associated with this PaymentIntent, if one was supplied. + * @property null|string $statement_descriptor

    Text that appears on the customer's statement as the statement descriptor for a non-card charge. This value overrides the account's default statement descriptor. For information about requirements, including the 22-character limit, see the Statement Descriptor docs.

    Setting this value for a card charge returns an error. For card charges, set the statement_descriptor_suffix instead.

    + * @property null|string $statement_descriptor_suffix Provides information about a card charge. Concatenated to the account's statement descriptor prefix to form the complete statement descriptor that appears on the customer's statement. + * @property string $status Status of this PaymentIntent, one of requires_payment_method, requires_confirmation, requires_action, processing, requires_capture, canceled, or succeeded. Read more about each PaymentIntent status. + * @property null|\EDD\Vendor\Stripe\StripeObject $transfer_data The data that automatically creates a Transfer after the payment finalizes. Learn more about the use case for connected accounts. + * @property null|string $transfer_group A string that identifies the resulting payment as part of a group. Learn more about the use case for connected accounts. + */ +class PaymentIntent extends ApiResource +{ + const OBJECT_NAME = 'payment_intent'; + + use ApiOperations\Update; + + const CANCELLATION_REASON_ABANDONED = 'abandoned'; + const CANCELLATION_REASON_AUTOMATIC = 'automatic'; + const CANCELLATION_REASON_DUPLICATE = 'duplicate'; + const CANCELLATION_REASON_FAILED_INVOICE = 'failed_invoice'; + const CANCELLATION_REASON_FRAUDULENT = 'fraudulent'; + const CANCELLATION_REASON_REQUESTED_BY_CUSTOMER = 'requested_by_customer'; + const CANCELLATION_REASON_VOID_INVOICE = 'void_invoice'; + + const CAPTURE_METHOD_AUTOMATIC = 'automatic'; + const CAPTURE_METHOD_AUTOMATIC_ASYNC = 'automatic_async'; + const CAPTURE_METHOD_MANUAL = 'manual'; + + const CONFIRMATION_METHOD_AUTOMATIC = 'automatic'; + const CONFIRMATION_METHOD_MANUAL = 'manual'; + + const SETUP_FUTURE_USAGE_OFF_SESSION = 'off_session'; + const SETUP_FUTURE_USAGE_ON_SESSION = 'on_session'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_PROCESSING = 'processing'; + const STATUS_REQUIRES_ACTION = 'requires_action'; + const STATUS_REQUIRES_CAPTURE = 'requires_capture'; + const STATUS_REQUIRES_CONFIRMATION = 'requires_confirmation'; + const STATUS_REQUIRES_PAYMENT_METHOD = 'requires_payment_method'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * Creates a PaymentIntent object. + * + * After the PaymentIntent is created, attach a payment method and confirm to continue the payment. + * Learn more about the available payment + * flows with the Payment Intents API. + * + * When you use confirm=true during creation, it’s equivalent to + * creating and confirming the PaymentIntent in the same call. You can use any + * parameters available in the confirm + * API when you supply confirm=true. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of PaymentIntents. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PaymentIntent> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a PaymentIntent that has previously been created. + * + * You can retrieve a PaymentIntent client-side using a publishable key when the + * client_secret is in the query string. + * + * If you retrieve a PaymentIntent with a publishable key, it only returns a subset + * of properties. Refer to the payment intent + * object reference for more details. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates properties on a PaymentIntent object without confirming. + * + * Depending on which properties you update, you might need to confirm the + * PaymentIntent again. For example, updating the payment_method + * always requires you to confirm the PaymentIntent again. If you prefer to update + * and confirm at the same time, we recommend updating properties through the confirm API instead. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent the applied payment intent + */ + public function applyCustomerBalance($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/apply_customer_balance'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent the canceled payment intent + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent the captured payment intent + */ + public function capture($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/capture'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent the confirmed payment intent + */ + public function confirm($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/confirm'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent the incremented payment intent + */ + public function incrementAuthorization($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/increment_authorization'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent the verified payment intent + */ + public function verifyMicrodeposits($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/verify_microdeposits'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\PaymentIntent> the payment intent search results + */ + public static function search($params = null, $opts = null) + { + $url = '/v1/payment_intents/search'; + + return static::_requestPage($url, \EDD\Vendor\Stripe\SearchResult::class, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/PaymentLink.php b/libraries/Stripe/lib/PaymentLink.php new file mode 100644 index 00000000000..b3bca883fb5 --- /dev/null +++ b/libraries/Stripe/lib/PaymentLink.php @@ -0,0 +1,167 @@ +checkout session to render the payment page. You can use checkout session events to track payments through payment links. + * + * Related guide: Payment Links API + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $active Whether the payment link's url is active. If false, customers visiting the URL will be shown a page saying that the link has been deactivated. + * @property \EDD\Vendor\Stripe\StripeObject $after_completion + * @property bool $allow_promotion_codes Whether user redeemable promotion codes are enabled. + * @property null|string|\EDD\Vendor\Stripe\Application $application The ID of the Connect application that created the Payment Link. + * @property null|int $application_fee_amount The amount of the application fee (if any) that will be requested to be applied to the payment and transferred to the application owner's EDD\Vendor\Stripe account. + * @property null|float $application_fee_percent This represents the percentage of the subscription invoice total that will be transferred to the application owner's EDD\Vendor\Stripe account. + * @property \EDD\Vendor\Stripe\StripeObject $automatic_tax + * @property string $billing_address_collection Configuration for collecting the customer's billing address. Defaults to auto. + * @property null|\EDD\Vendor\Stripe\StripeObject $consent_collection When set, provides configuration to gather active consent from customers. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property \EDD\Vendor\Stripe\StripeObject[] $custom_fields Collect additional information from your customer using custom fields. Up to 3 fields are supported. + * @property \EDD\Vendor\Stripe\StripeObject $custom_text + * @property string $customer_creation Configuration for Customer creation during checkout. + * @property null|string $inactive_message The custom message to be displayed to a customer when a payment link is no longer active. + * @property null|\EDD\Vendor\Stripe\StripeObject $invoice_creation Configuration for creating invoice for payment mode payment links. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> $line_items The line items representing what is being sold. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string|\EDD\Vendor\Stripe\Account $on_behalf_of The account on behalf of which to charge. See the Connect documentation for details. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_intent_data Indicates the parameters to be passed to PaymentIntent creation during checkout. + * @property string $payment_method_collection Configuration for collecting a payment method during checkout. Defaults to always. + * @property null|string[] $payment_method_types The list of payment method types that customers can use. When null, EDD\Vendor\Stripe will dynamically show relevant payment methods you've enabled in your payment method settings. + * @property \EDD\Vendor\Stripe\StripeObject $phone_number_collection + * @property null|\EDD\Vendor\Stripe\StripeObject $restrictions Settings that restrict the usage of a payment link. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_address_collection Configuration for collecting the customer's shipping address. + * @property \EDD\Vendor\Stripe\StripeObject[] $shipping_options The shipping rate options applied to the session. + * @property string $submit_type Indicates the type of transaction being performed which customizes relevant text on the page, such as the submit button. + * @property null|\EDD\Vendor\Stripe\StripeObject $subscription_data When creating a subscription, the specified configuration data will be used. There must be at least one line item with a recurring price to use subscription_data. + * @property \EDD\Vendor\Stripe\StripeObject $tax_id_collection + * @property null|\EDD\Vendor\Stripe\StripeObject $transfer_data The account (if any) the payments will be attributed to for tax reporting, and where funds from each payment will be transferred to. + * @property string $url The public URL that can be shared with customers. + */ +class PaymentLink extends ApiResource +{ + const OBJECT_NAME = 'payment_link'; + + use ApiOperations\Update; + + const BILLING_ADDRESS_COLLECTION_AUTO = 'auto'; + const BILLING_ADDRESS_COLLECTION_REQUIRED = 'required'; + + const CUSTOMER_CREATION_ALWAYS = 'always'; + const CUSTOMER_CREATION_IF_REQUIRED = 'if_required'; + + const PAYMENT_METHOD_COLLECTION_ALWAYS = 'always'; + const PAYMENT_METHOD_COLLECTION_IF_REQUIRED = 'if_required'; + + const SUBMIT_TYPE_AUTO = 'auto'; + const SUBMIT_TYPE_BOOK = 'book'; + const SUBMIT_TYPE_DONATE = 'donate'; + const SUBMIT_TYPE_PAY = 'pay'; + + /** + * Creates a payment link. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentLink the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of your payment links. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PaymentLink> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieve a payment link. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentLink + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a payment link. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentLink the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> list of line items + */ + public static function allLineItems($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/line_items'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/PaymentMethod.php b/libraries/Stripe/lib/PaymentMethod.php new file mode 100644 index 00000000000..985bfed077f --- /dev/null +++ b/libraries/Stripe/lib/PaymentMethod.php @@ -0,0 +1,243 @@ +PaymentIntents to collect payments or save them to + * Customer objects to store instrument details for future payments. + * + * Related guides: Payment Methods and More Payment Scenarios. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $acss_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $affirm + * @property null|\EDD\Vendor\Stripe\StripeObject $afterpay_clearpay + * @property null|\EDD\Vendor\Stripe\StripeObject $alipay + * @property null|string $allow_redisplay This field indicates whether this payment method can be shown again to its customer in a checkout flow. EDD\Vendor\Stripe products such as Checkout and Elements use this field to determine whether a payment method can be shown as a saved payment method in a checkout flow. The field defaults to “unspecified”. + * @property null|\EDD\Vendor\Stripe\StripeObject $amazon_pay + * @property null|\EDD\Vendor\Stripe\StripeObject $au_becs_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $bacs_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $bancontact + * @property \EDD\Vendor\Stripe\StripeObject $billing_details + * @property null|\EDD\Vendor\Stripe\StripeObject $blik + * @property null|\EDD\Vendor\Stripe\StripeObject $boleto + * @property null|\EDD\Vendor\Stripe\StripeObject $card + * @property null|\EDD\Vendor\Stripe\StripeObject $card_present + * @property null|\EDD\Vendor\Stripe\StripeObject $cashapp + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The ID of the Customer to which this PaymentMethod is saved. This will not be set when the PaymentMethod has not been saved to a Customer. + * @property null|\EDD\Vendor\Stripe\StripeObject $customer_balance + * @property null|\EDD\Vendor\Stripe\StripeObject $eps + * @property null|\EDD\Vendor\Stripe\StripeObject $fpx + * @property null|\EDD\Vendor\Stripe\StripeObject $giropay + * @property null|\EDD\Vendor\Stripe\StripeObject $grabpay + * @property null|\EDD\Vendor\Stripe\StripeObject $ideal + * @property null|\EDD\Vendor\Stripe\StripeObject $interac_present + * @property null|\EDD\Vendor\Stripe\StripeObject $klarna + * @property null|\EDD\Vendor\Stripe\StripeObject $konbini + * @property null|\EDD\Vendor\Stripe\StripeObject $link + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $mobilepay + * @property null|\EDD\Vendor\Stripe\StripeObject $multibanco + * @property null|\EDD\Vendor\Stripe\StripeObject $oxxo + * @property null|\EDD\Vendor\Stripe\StripeObject $p24 + * @property null|\EDD\Vendor\Stripe\StripeObject $paynow + * @property null|\EDD\Vendor\Stripe\StripeObject $paypal + * @property null|\EDD\Vendor\Stripe\StripeObject $pix + * @property null|\EDD\Vendor\Stripe\StripeObject $promptpay + * @property null|\EDD\Vendor\Stripe\StripeObject $radar_options Options to configure Radar. See Radar Session for more information. + * @property null|\EDD\Vendor\Stripe\StripeObject $revolut_pay + * @property null|\EDD\Vendor\Stripe\StripeObject $sepa_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $sofort + * @property null|\EDD\Vendor\Stripe\StripeObject $swish + * @property null|\EDD\Vendor\Stripe\StripeObject $twint + * @property string $type The type of the PaymentMethod. An additional hash is included on the PaymentMethod with a name matching this value. It contains additional information specific to the PaymentMethod type. + * @property null|\EDD\Vendor\Stripe\StripeObject $us_bank_account + * @property null|\EDD\Vendor\Stripe\StripeObject $wechat_pay + * @property null|\EDD\Vendor\Stripe\StripeObject $zip + */ +class PaymentMethod extends ApiResource +{ + const OBJECT_NAME = 'payment_method'; + + use ApiOperations\Update; + + const ALLOW_REDISPLAY_ALWAYS = 'always'; + const ALLOW_REDISPLAY_LIMITED = 'limited'; + const ALLOW_REDISPLAY_UNSPECIFIED = 'unspecified'; + + const TYPE_ACSS_DEBIT = 'acss_debit'; + const TYPE_AFFIRM = 'affirm'; + const TYPE_AFTERPAY_CLEARPAY = 'afterpay_clearpay'; + const TYPE_ALIPAY = 'alipay'; + const TYPE_AMAZON_PAY = 'amazon_pay'; + const TYPE_AU_BECS_DEBIT = 'au_becs_debit'; + const TYPE_BACS_DEBIT = 'bacs_debit'; + const TYPE_BANCONTACT = 'bancontact'; + const TYPE_BLIK = 'blik'; + const TYPE_BOLETO = 'boleto'; + const TYPE_CARD = 'card'; + const TYPE_CARD_PRESENT = 'card_present'; + const TYPE_CASHAPP = 'cashapp'; + const TYPE_CUSTOMER_BALANCE = 'customer_balance'; + const TYPE_EPS = 'eps'; + const TYPE_FPX = 'fpx'; + const TYPE_GIROPAY = 'giropay'; + const TYPE_GRABPAY = 'grabpay'; + const TYPE_IDEAL = 'ideal'; + const TYPE_INTERAC_PRESENT = 'interac_present'; + const TYPE_KLARNA = 'klarna'; + const TYPE_KONBINI = 'konbini'; + const TYPE_LINK = 'link'; + const TYPE_MOBILEPAY = 'mobilepay'; + const TYPE_MULTIBANCO = 'multibanco'; + const TYPE_OXXO = 'oxxo'; + const TYPE_P24 = 'p24'; + const TYPE_PAYNOW = 'paynow'; + const TYPE_PAYPAL = 'paypal'; + const TYPE_PIX = 'pix'; + const TYPE_PROMPTPAY = 'promptpay'; + const TYPE_REVOLUT_PAY = 'revolut_pay'; + const TYPE_SEPA_DEBIT = 'sepa_debit'; + const TYPE_SOFORT = 'sofort'; + const TYPE_SWISH = 'swish'; + const TYPE_TWINT = 'twint'; + const TYPE_US_BANK_ACCOUNT = 'us_bank_account'; + const TYPE_WECHAT_PAY = 'wechat_pay'; + const TYPE_ZIP = 'zip'; + + /** + * Creates a PaymentMethod object. Read the Stripe.js + * reference to learn how to create PaymentMethods via Stripe.js. + * + * Instead of creating a PaymentMethod directly, we recommend using the PaymentIntents API to accept a + * payment immediately or the SetupIntent API to collect payment + * method details ahead of a future payment. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of PaymentMethods for Treasury flows. If you want to list the + * PaymentMethods attached to a Customer for payments, you should use the List a Customer’s + * PaymentMethods API instead. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PaymentMethod> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a PaymentMethod object attached to the StripeAccount. To retrieve a + * payment method attached to a Customer, you should use Retrieve a Customer’s + * PaymentMethods. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a PaymentMethod object. A PaymentMethod must be attached a customer to + * be updated. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod the attached payment method + */ + public function attach($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/attach'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod the detached payment method + */ + public function detach($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/detach'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/PaymentMethodConfiguration.php b/libraries/Stripe/lib/PaymentMethodConfiguration.php new file mode 100644 index 00000000000..3b3eadc3b46 --- /dev/null +++ b/libraries/Stripe/lib/PaymentMethodConfiguration.php @@ -0,0 +1,158 @@ +charge type: + * + * Direct configurations apply to payments created on your account, including Connect destination charges, Connect separate charges and transfers, and payments not involving Connect. + * + * Child configurations apply to payments created on your connected accounts using direct charges, and charges with the on_behalf_of parameter. + * + * Child configurations have a parent that sets default values and controls which settings connected accounts may override. You can specify a parent ID at payment time, and EDD\Vendor\Stripe will automatically resolve the connected account’s associated child configuration. Parent configurations are managed in the dashboard and are not available in this API. + * + * Related guides: + * - Payment Method Configurations API + * - Multiple configurations on dynamic payment methods + * - Multiple configurations for your Connect accounts + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $acss_debit + * @property bool $active Whether the configuration can be used for new payments. + * @property null|\EDD\Vendor\Stripe\StripeObject $affirm + * @property null|\EDD\Vendor\Stripe\StripeObject $afterpay_clearpay + * @property null|\EDD\Vendor\Stripe\StripeObject $alipay + * @property null|\EDD\Vendor\Stripe\StripeObject $amazon_pay + * @property null|\EDD\Vendor\Stripe\StripeObject $apple_pay + * @property null|string $application For child configs, the Connect application associated with the configuration. + * @property null|\EDD\Vendor\Stripe\StripeObject $au_becs_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $bacs_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $bancontact + * @property null|\EDD\Vendor\Stripe\StripeObject $blik + * @property null|\EDD\Vendor\Stripe\StripeObject $boleto + * @property null|\EDD\Vendor\Stripe\StripeObject $card + * @property null|\EDD\Vendor\Stripe\StripeObject $cartes_bancaires + * @property null|\EDD\Vendor\Stripe\StripeObject $cashapp + * @property null|\EDD\Vendor\Stripe\StripeObject $customer_balance + * @property null|\EDD\Vendor\Stripe\StripeObject $eps + * @property null|\EDD\Vendor\Stripe\StripeObject $fpx + * @property null|\EDD\Vendor\Stripe\StripeObject $giropay + * @property null|\EDD\Vendor\Stripe\StripeObject $google_pay + * @property null|\EDD\Vendor\Stripe\StripeObject $grabpay + * @property null|\EDD\Vendor\Stripe\StripeObject $ideal + * @property bool $is_default The default configuration is used whenever a payment method configuration is not specified. + * @property null|\EDD\Vendor\Stripe\StripeObject $jcb + * @property null|\EDD\Vendor\Stripe\StripeObject $klarna + * @property null|\EDD\Vendor\Stripe\StripeObject $konbini + * @property null|\EDD\Vendor\Stripe\StripeObject $link + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $mobilepay + * @property null|\EDD\Vendor\Stripe\StripeObject $multibanco + * @property string $name The configuration's name. + * @property null|\EDD\Vendor\Stripe\StripeObject $oxxo + * @property null|\EDD\Vendor\Stripe\StripeObject $p24 + * @property null|string $parent For child configs, the configuration's parent configuration. + * @property null|\EDD\Vendor\Stripe\StripeObject $paynow + * @property null|\EDD\Vendor\Stripe\StripeObject $paypal + * @property null|\EDD\Vendor\Stripe\StripeObject $promptpay + * @property null|\EDD\Vendor\Stripe\StripeObject $revolut_pay + * @property null|\EDD\Vendor\Stripe\StripeObject $sepa_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $sofort + * @property null|\EDD\Vendor\Stripe\StripeObject $swish + * @property null|\EDD\Vendor\Stripe\StripeObject $twint + * @property null|\EDD\Vendor\Stripe\StripeObject $us_bank_account + * @property null|\EDD\Vendor\Stripe\StripeObject $wechat_pay + * @property null|\EDD\Vendor\Stripe\StripeObject $zip + */ +class PaymentMethodConfiguration extends ApiResource +{ + const OBJECT_NAME = 'payment_method_configuration'; + + use ApiOperations\Update; + + /** + * Creates a payment method configuration. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodConfiguration the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * List payment method configurations. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PaymentMethodConfiguration> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieve payment method configuration. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodConfiguration + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Update payment method configuration. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodConfiguration the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/PaymentMethodDomain.php b/libraries/Stripe/lib/PaymentMethodDomain.php new file mode 100644 index 00000000000..a86b643c5bc --- /dev/null +++ b/libraries/Stripe/lib/PaymentMethodDomain.php @@ -0,0 +1,127 @@ +Payment method domains. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property \EDD\Vendor\Stripe\StripeObject $apple_pay Indicates the status of a specific payment method on a payment method domain. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $domain_name The domain name that this payment method domain object represents. + * @property bool $enabled Whether this payment method domain is enabled. If the domain is not enabled, payment methods that require a payment method domain will not appear in Elements. + * @property \EDD\Vendor\Stripe\StripeObject $google_pay Indicates the status of a specific payment method on a payment method domain. + * @property \EDD\Vendor\Stripe\StripeObject $link Indicates the status of a specific payment method on a payment method domain. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $paypal Indicates the status of a specific payment method on a payment method domain. + */ +class PaymentMethodDomain extends ApiResource +{ + const OBJECT_NAME = 'payment_method_domain'; + + use ApiOperations\Update; + + /** + * Creates a payment method domain. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodDomain the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Lists the details of existing payment method domains. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PaymentMethodDomain> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing payment method domain. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodDomain + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates an existing payment method domain. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodDomain the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodDomain the validated payment method domain + */ + public function validate($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/validate'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Payout.php b/libraries/Stripe/lib/Payout.php new file mode 100644 index 00000000000..bfc95636110 --- /dev/null +++ b/libraries/Stripe/lib/Payout.php @@ -0,0 +1,211 @@ +Payout object is created when you receive funds from Stripe, or when you + * initiate a payout to either a bank account or debit card of a connected + * EDD\Vendor\Stripe account. You can retrieve individual payouts, + * and list all payouts. Payouts are made on varying + * schedules, depending on your country and + * industry. + * + * Related guide: Receiving payouts + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount The amount (in cents (or local equivalent)) that transfers to your bank account or debit card. + * @property null|string|\EDD\Vendor\Stripe\ApplicationFee $application_fee The application fee (if any) for the payout. See the Connect documentation for details. + * @property null|int $application_fee_amount The amount of the application fee (if any) requested for the payout. See the Connect documentation for details. + * @property int $arrival_date Date that you can expect the payout to arrive in the bank. This factors in delays to account for weekends or bank holidays. + * @property bool $automatic Returns true if the payout is created by an automated payout schedule and false if it's requested manually. + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $balance_transaction ID of the balance transaction that describes the impact of this payout on your account balance. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|string|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card $destination ID of the bank account or card the payout is sent to. + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $failure_balance_transaction If the payout fails or cancels, this is the ID of the balance transaction that reverses the initial balance transaction and returns the funds from the failed payout back in your balance. + * @property null|string $failure_code Error code that provides a reason for a payout failure, if available. View our list of failure codes. + * @property null|string $failure_message Message that provides the reason for a payout failure, if available. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $method The method used to send this payout, which can be standard or instant. instant is supported for payouts to debit cards and bank accounts in certain countries. Learn more about bank support for Instant Payouts. + * @property null|string|\EDD\Vendor\Stripe\Payout $original_payout If the payout reverses another, this is the ID of the original payout. + * @property string $reconciliation_status If completed, you can use the Balance Transactions API to list all balance transactions that are paid out in this payout. + * @property null|string|\EDD\Vendor\Stripe\Payout $reversed_by If the payout reverses, this is the ID of the payout that reverses this payout. + * @property string $source_type The source balance this payout came from, which can be one of the following: card, fpx, or bank_account. + * @property null|string $statement_descriptor Extra information about a payout that displays on the user's bank statement. + * @property string $status Current status of the payout: paid, pending, in_transit, canceled or failed. A payout is pending until it's submitted to the bank, when it becomes in_transit. The status changes to paid if the transaction succeeds, or to failed or canceled (within 5 business days). Some payouts that fail might initially show as paid, then change to failed. + * @property string $type Can be bank_account or card. + */ +class Payout extends ApiResource +{ + const OBJECT_NAME = 'payout'; + + use ApiOperations\Update; + + const METHOD_INSTANT = 'instant'; + const METHOD_STANDARD = 'standard'; + + const RECONCILIATION_STATUS_COMPLETED = 'completed'; + const RECONCILIATION_STATUS_IN_PROGRESS = 'in_progress'; + const RECONCILIATION_STATUS_NOT_APPLICABLE = 'not_applicable'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_FAILED = 'failed'; + const STATUS_IN_TRANSIT = 'in_transit'; + const STATUS_PAID = 'paid'; + const STATUS_PENDING = 'pending'; + + const TYPE_BANK_ACCOUNT = 'bank_account'; + const TYPE_CARD = 'card'; + + /** + * To send funds to your own bank account, create a new payout object. Your EDD\Vendor\Stripe balance must cover the payout amount. If it doesn’t, + * you receive an “Insufficient Funds” error. + * + * If your API key is in test mode, money won’t actually be sent, though every + * other action occurs as if you’re in live mode. + * + * If you create a manual payout on a EDD\Vendor\Stripe account that uses multiple payment + * source types, you need to specify the source type balance that the payout draws + * from. The balance object details available and + * pending amounts by source type. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of existing payouts sent to third-party bank accounts or payouts + * that EDD\Vendor\Stripe sent to you. The payouts return in sorted order, with the most + * recently created payouts appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Payout> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing payout. Supply the unique payout ID from + * either a payout creation request or the payout list. EDD\Vendor\Stripe returns the + * corresponding payout information. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified payout by setting the values of the parameters you pass. + * We don’t change parameters that you don’t provide. This request only accepts the + * metadata as arguments. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + const FAILURE_ACCOUNT_CLOSED = 'account_closed'; + const FAILURE_ACCOUNT_FROZEN = 'account_frozen'; + const FAILURE_BANK_ACCOUNT_RESTRICTED = 'bank_account_restricted'; + const FAILURE_BANK_OWNERSHIP_CHANGED = 'bank_ownership_changed'; + const FAILURE_COULD_NOT_PROCESS = 'could_not_process'; + const FAILURE_DEBIT_NOT_AUTHORIZED = 'debit_not_authorized'; + const FAILURE_DECLINED = 'declined'; + const FAILURE_INCORRECT_ACCOUNT_HOLDER_ADDRESS = 'incorrect_account_holder_address'; + const FAILURE_INCORRECT_ACCOUNT_HOLDER_NAME = 'incorrect_account_holder_name'; + const FAILURE_INCORRECT_ACCOUNT_HOLDER_TAX_ID = 'incorrect_account_holder_tax_id'; + const FAILURE_INSUFFICIENT_FUNDS = 'insufficient_funds'; + const FAILURE_INVALID_ACCOUNT_NUMBER = 'invalid_account_number'; + const FAILURE_INVALID_CURRENCY = 'invalid_currency'; + const FAILURE_NO_ACCOUNT = 'no_account'; + const FAILURE_UNSUPPORTED_CARD = 'unsupported_card'; + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout the canceled payout + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout the reversed payout + */ + public function reverse($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/reverse'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Person.php b/libraries/Stripe/lib/Person.php new file mode 100644 index 00000000000..201d3fa8f94 --- /dev/null +++ b/libraries/Stripe/lib/Person.php @@ -0,0 +1,139 @@ +account.controller.requirement_collection is stripe, which includes Standard and Express accounts, after creating an Account Link or Account Session to start Connect onboarding. + * + * See the Standard onboarding or Express onboarding documentation for information about prefilling information and account onboarding steps. Learn more about handling identity verification with the API. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $account The account the person is associated with. + * @property null|\EDD\Vendor\Stripe\StripeObject $additional_tos_acceptances + * @property null|\EDD\Vendor\Stripe\StripeObject $address + * @property null|\EDD\Vendor\Stripe\StripeObject $address_kana The Kana variation of the person's address (Japan only). + * @property null|\EDD\Vendor\Stripe\StripeObject $address_kanji The Kanji variation of the person's address (Japan only). + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|\EDD\Vendor\Stripe\StripeObject $dob + * @property null|string $email The person's email address. + * @property null|string $first_name The person's first name. + * @property null|string $first_name_kana The Kana variation of the person's first name (Japan only). + * @property null|string $first_name_kanji The Kanji variation of the person's first name (Japan only). + * @property null|string[] $full_name_aliases A list of alternate names or aliases that the person is known by. + * @property null|\EDD\Vendor\Stripe\StripeObject $future_requirements Information about the upcoming new requirements for this person, including what information needs to be collected, and by when. + * @property null|string $gender The person's gender (International regulations require either "male" or "female"). + * @property null|bool $id_number_provided Whether the person's id_number was provided. True if either the full ID number was provided or if only the required part of the ID number was provided (ex. last four of an individual's SSN for the US indicated by ssn_last_4_provided). + * @property null|bool $id_number_secondary_provided Whether the person's id_number_secondary was provided. + * @property null|string $last_name The person's last name. + * @property null|string $last_name_kana The Kana variation of the person's last name (Japan only). + * @property null|string $last_name_kanji The Kanji variation of the person's last name (Japan only). + * @property null|string $maiden_name The person's maiden name. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $nationality The country where the person is a national. + * @property null|string $phone The person's phone number. + * @property null|string $political_exposure Indicates if the person or any of their representatives, family members, or other closely related persons, declares that they hold or have held an important public job or function, in any jurisdiction. + * @property null|\EDD\Vendor\Stripe\StripeObject $registered_address + * @property null|\EDD\Vendor\Stripe\StripeObject $relationship + * @property null|\EDD\Vendor\Stripe\StripeObject $requirements Information about the requirements for this person, including what information needs to be collected, and by when. + * @property null|bool $ssn_last_4_provided Whether the last four digits of the person's Social Security number have been provided (U.S. only). + * @property null|\EDD\Vendor\Stripe\StripeObject $verification + */ +class Person extends ApiResource +{ + const OBJECT_NAME = 'person'; + + const GENDER_FEMALE = 'female'; + const GENDER_MALE = 'male'; + + const POLITICAL_EXPOSURE_EXISTING = 'existing'; + const POLITICAL_EXPOSURE_NONE = 'none'; + + const VERIFICATION_STATUS_PENDING = 'pending'; + const VERIFICATION_STATUS_UNVERIFIED = 'unverified'; + const VERIFICATION_STATUS_VERIFIED = 'verified'; + + use ApiOperations\Delete; + + /** + * @return string the API URL for this EDD\Vendor\Stripe account reversal + */ + public function instanceUrl() + { + $id = $this['id']; + $account = $this['account']; + if (!$id) { + throw new Exception\UnexpectedValueException( + 'Could not determine which URL to request: ' . + "class instance has invalid ID: {$id}", + null + ); + } + $id = Util\Util::utf8($id); + $account = Util\Util::utf8($account); + + $base = Account::classUrl(); + $accountExtn = \urlencode($account); + $extn = \urlencode($id); + + return "{$base}/{$accountExtn}/persons/{$extn}"; + } + + /** + * @param array|string $_id + * @param null|array|string $_opts + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function retrieve($_id, $_opts = null) + { + $msg = 'Persons cannot be retrieved without an account ID. Retrieve ' . + "a person using `Account::retrievePerson('account_id', " . + "'person_id')`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param string $_id + * @param null|array $_params + * @param null|array|string $_options + * + * @throws \EDD\Vendor\Stripe\Exception\BadMethodCallException + */ + public static function update($_id, $_params = null, $_options = null) + { + $msg = 'Persons cannot be updated without an account ID. Update ' . + "a person using `Account::updatePerson('account_id', " . + "'person_id', \$updateParams)`."; + + throw new Exception\BadMethodCallException($msg); + } + + /** + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return static the saved resource + * + * @deprecated The `save` method is deprecated and will be removed in a + * future major version of the library. Use the static method `update` + * on the resource instead. + */ + public function save($opts = null) + { + $params = $this->serializeParameters(); + if (\count($params) > 0) { + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']); + $this->refreshFrom($response, $opts); + } + + return $this; + } +} diff --git a/libraries/Stripe/lib/Plan.php b/libraries/Stripe/lib/Plan.php new file mode 100644 index 00000000000..8cd3bbddcf1 --- /dev/null +++ b/libraries/Stripe/lib/Plan.php @@ -0,0 +1,170 @@ +Prices API. It replaces the Plans API and is backwards compatible to simplify your migration. + * + * Plans define the base price, currency, and billing cycle for recurring purchases of products. + * Products help you track inventory or provisioning, and plans help you track pricing. Different physical goods or levels of service should be represented by products, and pricing options should be represented by plans. This approach lets you change prices without having to change your provisioning scheme. + * + * For example, you might have a single "gold" product that has plans for $10/month, $100/year, €9/month, and €90/year. + * + * Related guides: Set up a subscription and more about products and prices. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $active Whether the plan can be used for new purchases. + * @property null|string $aggregate_usage Specifies a usage aggregation strategy for plans of usage_type=metered. Allowed values are sum for summing up all usage during a period, last_during_period for using the last usage record reported within a period, last_ever for using the last usage record ever (across period bounds) or max which uses the usage record with the maximum reported usage during a period. Defaults to sum. + * @property null|int $amount The unit amount in cents (or local equivalent) to be charged, represented as a whole integer if possible. Only set if billing_scheme=per_unit. + * @property null|string $amount_decimal The unit amount in cents (or local equivalent) to be charged, represented as a decimal string with at most 12 decimal places. Only set if billing_scheme=per_unit. + * @property string $billing_scheme Describes how to compute the price per period. Either per_unit or tiered. per_unit indicates that the fixed amount (specified in amount) will be charged per unit in quantity (for plans with usage_type=licensed), or per unit of total usage (for plans with usage_type=metered). tiered indicates that the unit pricing will be computed using a tiering strategy as defined using the tiers and tiers_mode attributes. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string $interval The frequency at which a subscription is billed. One of day, week, month or year. + * @property int $interval_count The number of intervals (specified in the interval attribute) between subscription billings. For example, interval=month and interval_count=3 bills every 3 months. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $meter The meter tracking the usage of a metered price + * @property null|string $nickname A brief description of the plan, hidden from customers. + * @property null|string|\EDD\Vendor\Stripe\Product $product The product whose pricing this plan determines. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $tiers Each element represents a pricing tier. This parameter requires billing_scheme to be set to tiered. See also the documentation for billing_scheme. + * @property null|string $tiers_mode Defines if the tiering price should be graduated or volume based. In volume-based tiering, the maximum quantity within a period determines the per unit price. In graduated tiering, pricing can change as the quantity grows. + * @property null|\EDD\Vendor\Stripe\StripeObject $transform_usage Apply a transformation to the reported usage or set quantity before computing the amount billed. Cannot be combined with tiers. + * @property null|int $trial_period_days Default number of trial days when subscribing a customer to this plan using trial_from_plan=true. + * @property string $usage_type Configures how the quantity per period should be determined. Can be either metered or licensed. licensed automatically bills the quantity set when adding it to a subscription. metered aggregates the total usage based on usage records. Defaults to licensed. + */ +class Plan extends ApiResource +{ + const OBJECT_NAME = 'plan'; + + use ApiOperations\Update; + + const AGGREGATE_USAGE_LAST_DURING_PERIOD = 'last_during_period'; + const AGGREGATE_USAGE_LAST_EVER = 'last_ever'; + const AGGREGATE_USAGE_MAX = 'max'; + const AGGREGATE_USAGE_SUM = 'sum'; + + const BILLING_SCHEME_PER_UNIT = 'per_unit'; + const BILLING_SCHEME_TIERED = 'tiered'; + + const INTERVAL_DAY = 'day'; + const INTERVAL_MONTH = 'month'; + const INTERVAL_WEEK = 'week'; + const INTERVAL_YEAR = 'year'; + + const TIERS_MODE_GRADUATED = 'graduated'; + const TIERS_MODE_VOLUME = 'volume'; + + const USAGE_TYPE_LICENSED = 'licensed'; + const USAGE_TYPE_METERED = 'metered'; + + /** + * You can now model subscriptions more flexibly using the Prices + * API. It replaces the Plans API and is backwards compatible to simplify your + * migration. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Plan the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deleting plans means new subscribers can’t be added. Existing subscribers aren’t + * affected. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Plan the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of your plans. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Plan> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the plan with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Plan + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified plan by setting the values of the parameters passed. Any + * parameters not provided are left unchanged. By design, you cannot change a + * plan’s ID, amount, currency, or billing cycle. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Plan the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Price.php b/libraries/Stripe/lib/Price.php new file mode 100644 index 00000000000..83ffdd44374 --- /dev/null +++ b/libraries/Stripe/lib/Price.php @@ -0,0 +1,155 @@ +Products help you track inventory or provisioning, and prices help you track payment terms. Different physical goods or levels of service should be represented by products, and pricing options should be represented by prices. This approach lets you change prices without having to change your provisioning scheme. + * + * For example, you might have a single "gold" product that has prices for $10/month, $100/year, and €9 once. + * + * Related guides: Set up a subscription, create an invoice, and more about products and prices. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $active Whether the price can be used for new purchases. + * @property string $billing_scheme Describes how to compute the price per period. Either per_unit or tiered. per_unit indicates that the fixed amount (specified in unit_amount or unit_amount_decimal) will be charged per unit in quantity (for prices with usage_type=licensed), or per unit of total usage (for prices with usage_type=metered). tiered indicates that the unit pricing will be computed using a tiering strategy as defined using the tiers and tiers_mode attributes. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|\EDD\Vendor\Stripe\StripeObject $currency_options Prices defined in each available currency option. Each key must be a three-letter ISO currency code and a supported currency. + * @property null|\EDD\Vendor\Stripe\StripeObject $custom_unit_amount When set, provides configuration for the amount to be adjusted by the customer during Checkout Sessions and Payment Links. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $lookup_key A lookup key used to retrieve prices dynamically from a static string. This may be up to 200 characters. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $nickname A brief description of the price, hidden from customers. + * @property string|\EDD\Vendor\Stripe\Product $product The ID of the product this price is associated with. + * @property null|\EDD\Vendor\Stripe\StripeObject $recurring The recurring components of a price such as interval and usage_type. + * @property null|string $tax_behavior Only required if a default tax behavior was not provided in the EDD\Vendor\Stripe Tax settings. Specifies whether the price is considered inclusive of taxes or exclusive of taxes. One of inclusive, exclusive, or unspecified. Once specified as either inclusive or exclusive, it cannot be changed. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $tiers Each element represents a pricing tier. This parameter requires billing_scheme to be set to tiered. See also the documentation for billing_scheme. + * @property null|string $tiers_mode Defines if the tiering price should be graduated or volume based. In volume-based tiering, the maximum quantity within a period determines the per unit price. In graduated tiering, pricing can change as the quantity grows. + * @property null|\EDD\Vendor\Stripe\StripeObject $transform_quantity Apply a transformation to the reported usage or set quantity before computing the amount billed. Cannot be combined with tiers. + * @property string $type One of one_time or recurring depending on whether the price is for a one-time purchase or a recurring (subscription) purchase. + * @property null|int $unit_amount The unit amount in cents (or local equivalent) to be charged, represented as a whole integer if possible. Only set if billing_scheme=per_unit. + * @property null|string $unit_amount_decimal The unit amount in cents (or local equivalent) to be charged, represented as a decimal string with at most 12 decimal places. Only set if billing_scheme=per_unit. + */ +class Price extends ApiResource +{ + const OBJECT_NAME = 'price'; + + use ApiOperations\Update; + + const BILLING_SCHEME_PER_UNIT = 'per_unit'; + const BILLING_SCHEME_TIERED = 'tiered'; + + const TAX_BEHAVIOR_EXCLUSIVE = 'exclusive'; + const TAX_BEHAVIOR_INCLUSIVE = 'inclusive'; + const TAX_BEHAVIOR_UNSPECIFIED = 'unspecified'; + + const TIERS_MODE_GRADUATED = 'graduated'; + const TIERS_MODE_VOLUME = 'volume'; + + const TYPE_ONE_TIME = 'one_time'; + const TYPE_RECURRING = 'recurring'; + + /** + * Creates a new price for an existing product. The price can be recurring or + * one-time. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Price the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of your active prices, excluding inline prices. + * For the list of inactive prices, set active to false. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Price> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the price with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Price + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified price by setting the values of the parameters passed. Any + * parameters not provided are left unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Price the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Price> the price search results + */ + public static function search($params = null, $opts = null) + { + $url = '/v1/prices/search'; + + return static::_requestPage($url, \EDD\Vendor\Stripe\SearchResult::class, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Product.php b/libraries/Stripe/lib/Product.php new file mode 100644 index 00000000000..2e7e6f938d2 --- /dev/null +++ b/libraries/Stripe/lib/Product.php @@ -0,0 +1,229 @@ +Prices to configure pricing in Payment Links, Checkout, and Subscriptions. + * + * Related guides: Set up a subscription, + * share a Payment Link, + * accept payments with Checkout, + * and more about Products and Prices + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $active Whether the product is currently available for purchase. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string|\EDD\Vendor\Stripe\Price $default_price The ID of the Price object that is the default price for this product. + * @property null|string $description The product's description, meant to be displayable to the customer. Use this field to optionally store a long form explanation of the product being sold for your own rendering purposes. + * @property string[] $images A list of up to 8 URLs of images for this product, meant to be displayable to the customer. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject[] $marketing_features A list of up to 15 marketing features for this product. These are displayed in pricing tables. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $name The product's name, meant to be displayable to the customer. + * @property null|\EDD\Vendor\Stripe\StripeObject $package_dimensions The dimensions of this product for shipping purposes. + * @property null|bool $shippable Whether this product is shipped (i.e., physical goods). + * @property null|string $statement_descriptor Extra information about a product which will appear on your customer's credit card statement. In the case that multiple products are billed at once, the first statement descriptor will be used. Only used for subscription payments. + * @property null|string|\EDD\Vendor\Stripe\TaxCode $tax_code A tax code ID. + * @property string $type The type of the product. The product is either of type good, which is eligible for use with Orders and SKUs, or service, which is eligible for use with Subscriptions and Plans. + * @property null|string $unit_label A label that represents units of this product. When set, this will be included in customers' receipts, invoices, Checkout, and the customer portal. + * @property int $updated Time at which the object was last updated. Measured in seconds since the Unix epoch. + * @property null|string $url A URL of a publicly-accessible webpage for this product. + */ +class Product extends ApiResource +{ + const OBJECT_NAME = 'product'; + + use ApiOperations\NestedResource; + use ApiOperations\Update; + + const TYPE_GOOD = 'good'; + const TYPE_SERVICE = 'service'; + + /** + * Creates a new product object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Product the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Delete a product. Deleting a product is only possible if it has no prices + * associated with it. Additionally, deleting a product with type=good + * is only possible if it has no SKUs associated with it. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Product the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of your products. The products are returned sorted by creation + * date, with the most recently created products appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Product> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing product. Supply the unique product ID from + * either a product creation request or the product list, and EDD\Vendor\Stripe will return + * the corresponding product information. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Product + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specific product by setting the values of the parameters passed. Any + * parameters not provided will be left unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Product the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Product> the product search results + */ + public static function search($params = null, $opts = null) + { + $url = '/v1/products/search'; + + return static::_requestPage($url, \EDD\Vendor\Stripe\SearchResult::class, $params, $opts); + } + + const PATH_FEATURES = '/features'; + + /** + * @param string $id the ID of the product on which to retrieve the product features + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ProductFeature> the list of product features + */ + public static function allFeatures($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_FEATURES, $params, $opts); + } + + /** + * @param string $id the ID of the product on which to create the product feature + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ProductFeature + */ + public static function createFeature($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_FEATURES, $params, $opts); + } + + /** + * @param string $id the ID of the product to which the product feature belongs + * @param string $featureId the ID of the product feature to delete + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ProductFeature + */ + public static function deleteFeature($id, $featureId, $params = null, $opts = null) + { + return self::_deleteNestedResource($id, static::PATH_FEATURES, $featureId, $params, $opts); + } + + /** + * @param string $id the ID of the product to which the product feature belongs + * @param string $featureId the ID of the product feature to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ProductFeature + */ + public static function retrieveFeature($id, $featureId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_FEATURES, $featureId, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/ProductFeature.php b/libraries/Stripe/lib/ProductFeature.php new file mode 100644 index 00000000000..650f60986a3 --- /dev/null +++ b/libraries/Stripe/lib/ProductFeature.php @@ -0,0 +1,19 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + */ +class ProductFeature extends ApiResource +{ + const OBJECT_NAME = 'product_feature'; +} diff --git a/libraries/Stripe/lib/PromotionCode.php b/libraries/Stripe/lib/PromotionCode.php new file mode 100644 index 00000000000..e2ae23c6217 --- /dev/null +++ b/libraries/Stripe/lib/PromotionCode.php @@ -0,0 +1,116 @@ +coupon. It can be used to + * create multiple codes for a single coupon. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $active Whether the promotion code is currently active. A promotion code is only active if the coupon is also valid. + * @property string $code The customer-facing code. Regardless of case, this code must be unique across all active promotion codes for each customer. + * @property \EDD\Vendor\Stripe\Coupon $coupon A coupon contains information about a percent-off or amount-off discount you might want to apply to a customer. Coupons may be applied to subscriptions, invoices, checkout sessions, quotes, and more. Coupons do not work with conventional one-off charges or payment intents. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The customer that this promotion code can be used by. + * @property null|int $expires_at Date at which the promotion code can no longer be redeemed. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|int $max_redemptions Maximum number of times this promotion code can be redeemed. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property \EDD\Vendor\Stripe\StripeObject $restrictions + * @property int $times_redeemed Number of times this promotion code has been used. + */ +class PromotionCode extends ApiResource +{ + const OBJECT_NAME = 'promotion_code'; + + use ApiOperations\Update; + + /** + * A promotion code points to a coupon. You can optionally restrict the code to a + * specific customer, redemption limit, and expiration date. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PromotionCode the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of your promotion codes. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PromotionCode> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the promotion code with the given ID. In order to retrieve a promotion + * code by the customer-facing code use list with the desired + * code. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PromotionCode + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified promotion code by setting the values of the parameters + * passed. Most fields are, by design, not editable. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PromotionCode the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Quote.php b/libraries/Stripe/lib/Quote.php new file mode 100644 index 00000000000..07fd869e07a --- /dev/null +++ b/libraries/Stripe/lib/Quote.php @@ -0,0 +1,252 @@ +charge_automatically, or send_invoice. When charging automatically, EDD\Vendor\Stripe will attempt to pay invoices at the end of the subscription cycle or on finalization using the default payment method attached to the subscription or customer. When sending an invoice, EDD\Vendor\Stripe will email your customer an invoice with payment instructions and mark the subscription as active. Defaults to charge_automatically. + * @property \EDD\Vendor\Stripe\StripeObject $computed + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The customer which this quote belongs to. A customer is required before finalizing the quote. Once specified, it cannot be changed. + * @property null|(string|\EDD\Vendor\Stripe\TaxRate)[] $default_tax_rates The tax rates applied to this quote. + * @property null|string $description A description that will be displayed on the quote PDF. + * @property (string|\EDD\Vendor\Stripe\Discount)[] $discounts The discounts applied to this quote. + * @property int $expires_at The date on which the quote will be canceled if in open or draft status. Measured in seconds since the Unix epoch. + * @property null|string $footer A footer that will be displayed on the quote PDF. + * @property null|\EDD\Vendor\Stripe\StripeObject $from_quote Details of the quote that was cloned. See the cloning documentation for more details. + * @property null|string $header A header that will be displayed on the quote PDF. + * @property null|string|\EDD\Vendor\Stripe\Invoice $invoice The invoice that was created from this quote. + * @property \EDD\Vendor\Stripe\StripeObject $invoice_settings + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> $line_items A list of items the customer is being quoted for. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $number A unique number that identifies this particular quote. This number is assigned once the quote is finalized. + * @property null|string|\EDD\Vendor\Stripe\Account $on_behalf_of The account on behalf of which to charge. See the Connect documentation for details. + * @property string $status The status of the quote. + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property null|string|\EDD\Vendor\Stripe\Subscription $subscription The subscription that was created or updated from this quote. + * @property \EDD\Vendor\Stripe\StripeObject $subscription_data + * @property null|string|\EDD\Vendor\Stripe\SubscriptionSchedule $subscription_schedule The subscription schedule that was created or updated from this quote. + * @property null|string|\EDD\Vendor\Stripe\TestHelpers\TestClock $test_clock ID of the test clock this quote belongs to. + * @property \EDD\Vendor\Stripe\StripeObject $total_details + * @property null|\EDD\Vendor\Stripe\StripeObject $transfer_data The account (if any) the payments will be attributed to for tax reporting, and where funds from each payment will be transferred to for each of the invoices. + */ +class Quote extends ApiResource +{ + const OBJECT_NAME = 'quote'; + + use ApiOperations\Update; + + const COLLECTION_METHOD_CHARGE_AUTOMATICALLY = 'charge_automatically'; + const COLLECTION_METHOD_SEND_INVOICE = 'send_invoice'; + + const STATUS_ACCEPTED = 'accepted'; + const STATUS_CANCELED = 'canceled'; + const STATUS_DRAFT = 'draft'; + const STATUS_OPEN = 'open'; + + /** + * A quote models prices and services for a customer. Default options for + * header, description, footer, and + * expires_at can be set in the dashboard via the quote template. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of your quotes. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Quote> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the quote with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * A quote models prices and services for a customer. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote the accepted quote + */ + public function accept($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/accept'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote the canceled quote + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote the finalized quote + */ + public function finalizeQuote($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/finalize'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> list of line items + */ + public static function allComputedUpfrontLineItems($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/computed_upfront_line_items'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> list of line items + */ + public static function allLineItems($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/line_items'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param callable $readBodyChunkCallable + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return void + */ + public function pdf($readBodyChunkCallable, $params = null, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + if (!isset($opts->apiBase)) { + $opts->apiBase = \EDD\Vendor\Stripe\Stripe::$apiUploadBase; + } + $url = $this->instanceUrl() . '/pdf'; + $this->_requestStream('get', $url, $readBodyChunkCallable, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Radar/EarlyFraudWarning.php b/libraries/Stripe/lib/Radar/EarlyFraudWarning.php new file mode 100644 index 00000000000..66a68ea8394 --- /dev/null +++ b/libraries/Stripe/lib/Radar/EarlyFraudWarning.php @@ -0,0 +1,73 @@ +Early fraud warnings + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $actionable An EFW is actionable if it has not received a dispute and has not been fully refunded. You may wish to proactively refund a charge that receives an EFW, in order to avoid receiving a dispute later. + * @property string|\EDD\Vendor\Stripe\Charge $charge ID of the charge this early fraud warning is for, optionally expanded. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $fraud_type The type of fraud labelled by the issuer. One of card_never_received, fraudulent_card_application, made_with_counterfeit_card, made_with_lost_card, made_with_stolen_card, misc, unauthorized_use_of_card. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string|\EDD\Vendor\Stripe\PaymentIntent $payment_intent ID of the Payment Intent this early fraud warning is for, optionally expanded. + */ +class EarlyFraudWarning extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'radar.early_fraud_warning'; + + const FRAUD_TYPE_CARD_NEVER_RECEIVED = 'card_never_received'; + const FRAUD_TYPE_FRAUDULENT_CARD_APPLICATION = 'fraudulent_card_application'; + const FRAUD_TYPE_MADE_WITH_COUNTERFEIT_CARD = 'made_with_counterfeit_card'; + const FRAUD_TYPE_MADE_WITH_LOST_CARD = 'made_with_lost_card'; + const FRAUD_TYPE_MADE_WITH_STOLEN_CARD = 'made_with_stolen_card'; + const FRAUD_TYPE_MISC = 'misc'; + const FRAUD_TYPE_UNAUTHORIZED_USE_OF_CARD = 'unauthorized_use_of_card'; + + /** + * Returns a list of early fraud warnings. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Radar\EarlyFraudWarning> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an early fraud warning that has previously been + * created. + * + * Please refer to the early fraud + * warning object reference for more details. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\EarlyFraudWarning + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Radar/ValueList.php b/libraries/Stripe/lib/Radar/ValueList.php new file mode 100644 index 00000000000..af1fd43c348 --- /dev/null +++ b/libraries/Stripe/lib/Radar/ValueList.php @@ -0,0 +1,148 @@ +Default EDD\Vendor\Stripe lists + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string $alias The name of the value list for use in rules. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $created_by The name or email address of the user who created this value list. + * @property string $item_type The type of items in the value list. One of card_fingerprint, us_bank_account_fingerprint, sepa_debit_fingerprint, card_bin, email, ip_address, country, string, case_sensitive_string, or customer_id. + * @property \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Radar\ValueListItem> $list_items List of items contained within this value list. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $name The name of the value list. + */ +class ValueList extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'radar.value_list'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const ITEM_TYPE_CARD_BIN = 'card_bin'; + const ITEM_TYPE_CARD_FINGERPRINT = 'card_fingerprint'; + const ITEM_TYPE_CASE_SENSITIVE_STRING = 'case_sensitive_string'; + const ITEM_TYPE_COUNTRY = 'country'; + const ITEM_TYPE_CUSTOMER_ID = 'customer_id'; + const ITEM_TYPE_EMAIL = 'email'; + const ITEM_TYPE_IP_ADDRESS = 'ip_address'; + const ITEM_TYPE_SEPA_DEBIT_FINGERPRINT = 'sepa_debit_fingerprint'; + const ITEM_TYPE_STRING = 'string'; + const ITEM_TYPE_US_BANK_ACCOUNT_FINGERPRINT = 'us_bank_account_fingerprint'; + + /** + * Creates a new ValueList object, which can then be referenced in + * rules. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueList the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes a ValueList object, also deleting any items contained + * within the value list. To be deleted, a value list must not be referenced in any + * rules. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueList the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of ValueList objects. The objects are sorted in + * descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Radar\ValueList> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a ValueList object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueList + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a ValueList object by setting the values of the parameters + * passed. Any parameters not provided will be left unchanged. Note that + * item_type is immutable. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueList the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Radar/ValueListItem.php b/libraries/Stripe/lib/Radar/ValueListItem.php new file mode 100644 index 00000000000..a6cc08a3e33 --- /dev/null +++ b/libraries/Stripe/lib/Radar/ValueListItem.php @@ -0,0 +1,106 @@ +Managing list items + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $created_by The name or email address of the user who added this item to the value list. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $value The value of the item. + * @property string $value_list The identifier of the value list this item belongs to. + */ +class ValueListItem extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'radar.value_list_item'; + + /** + * Creates a new ValueListItem object, which is added to the specified + * parent value list. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueListItem the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes a ValueListItem object, removing it from its parent value + * list. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueListItem the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of ValueListItem objects. The objects are sorted in + * descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Radar\ValueListItem> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a ValueListItem object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueListItem + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/RecipientTransfer.php b/libraries/Stripe/lib/RecipientTransfer.php new file mode 100644 index 00000000000..7623383d650 --- /dev/null +++ b/libraries/Stripe/lib/RecipientTransfer.php @@ -0,0 +1,36 @@ +Refunds + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount, in cents (or local equivalent). + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $balance_transaction Balance transaction that describes the impact on your account balance. + * @property null|string|\EDD\Vendor\Stripe\Charge $charge ID of the charge that's refunded. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. You can use this for displaying to users (available on non-card refunds only). + * @property null|\EDD\Vendor\Stripe\StripeObject $destination_details + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $failure_balance_transaction After the refund fails, this balance transaction describes the adjustment made on your account balance that reverses the initial balance transaction. + * @property null|string $failure_reason Provides the reason for the refund failure. Possible values are: lost_or_stolen_card, expired_or_canceled_card, charge_for_pending_refund_disputed, insufficient_funds, declined, merchant_request, or unknown. + * @property null|string $instructions_email For payment methods without native refund support (for example, Konbini, PromptPay), provide an email address for the customer to receive refund instructions. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $next_action + * @property null|string|\EDD\Vendor\Stripe\PaymentIntent $payment_intent ID of the PaymentIntent that's refunded. + * @property null|string $reason Reason for the refund, which is either user-provided (duplicate, fraudulent, or requested_by_customer) or generated by EDD\Vendor\Stripe internally (expired_uncaptured_charge). + * @property null|string $receipt_number This is the transaction number that appears on email receipts sent for this refund. + * @property null|string|\EDD\Vendor\Stripe\TransferReversal $source_transfer_reversal The transfer reversal that's associated with the refund. Only present if the charge came from another EDD\Vendor\Stripe account. + * @property null|string $status Status of the refund. This can be pending, requires_action, succeeded, failed, or canceled. Learn more about failed refunds. + * @property null|string|\EDD\Vendor\Stripe\TransferReversal $transfer_reversal This refers to the transfer reversal object if the accompanying transfer reverses. This is only applicable if the charge was created using the destination parameter. + */ +class Refund extends ApiResource +{ + const OBJECT_NAME = 'refund'; + + use ApiOperations\Update; + + const FAILURE_REASON_EXPIRED_OR_CANCELED_CARD = 'expired_or_canceled_card'; + const FAILURE_REASON_LOST_OR_STOLEN_CARD = 'lost_or_stolen_card'; + const FAILURE_REASON_UNKNOWN = 'unknown'; + + const REASON_DUPLICATE = 'duplicate'; + const REASON_EXPIRED_UNCAPTURED_CHARGE = 'expired_uncaptured_charge'; + const REASON_FRAUDULENT = 'fraudulent'; + const REASON_REQUESTED_BY_CUSTOMER = 'requested_by_customer'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_FAILED = 'failed'; + const STATUS_PENDING = 'pending'; + const STATUS_REQUIRES_ACTION = 'requires_action'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * When you create a new refund, you must specify a Charge or a PaymentIntent + * object on which to create it. + * + * Creating a new refund will refund a charge that has previously been created but + * not yet refunded. Funds will be refunded to the credit or debit card that was + * originally charged. + * + * You can optionally refund only part of a charge. You can do so multiple times, + * until the entire charge has been refunded. + * + * Once entirely refunded, a charge can’t be refunded again. This method will raise + * an error when called on an already-refunded charge, or when trying to refund + * more money than is left on a charge. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of all refunds you created. We return the refunds in sorted + * order, with the most recent refunds appearing first. The 10 most recent refunds + * are always available by default on the Charge object. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Refund> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing refund. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the refund that you specify by setting the values of the passed + * parameters. Any parameters that you don’t provide remain unchanged. + * + * This request only accepts metadata as an argument. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund the canceled refund + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Reporting/ReportRun.php b/libraries/Stripe/lib/Reporting/ReportRun.php new file mode 100644 index 00000000000..eced3817e55 --- /dev/null +++ b/libraries/Stripe/lib/Reporting/ReportRun.php @@ -0,0 +1,90 @@ +API Access to Reports. + * + * Note that certain report types can only be run based on your live-mode data (not test-mode + * data), and will error when queried without a live-mode API key. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $error If something should go wrong during the run, a message about the failure (populated when status=failed). + * @property bool $livemode true if the report is run on live mode data and false if it is run on test mode data. + * @property \EDD\Vendor\Stripe\StripeObject $parameters + * @property string $report_type The ID of the report type to run, such as "balance.summary.1". + * @property null|\EDD\Vendor\Stripe\File $result The file object representing the result of the report run (populated when status=succeeded). + * @property string $status Status of this report run. This will be pending when the run is initially created. When the run finishes, this will be set to succeeded and the result field will be populated. Rarely, we may encounter an error, at which point this will be set to failed and the error field will be populated. + * @property null|int $succeeded_at Timestamp at which this run successfully finished (populated when status=succeeded). Measured in seconds since the Unix epoch. + */ +class ReportRun extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'reporting.report_run'; + + /** + * Creates a new object and begin running the report. (Certain report types require + * a live-mode API key.). + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Reporting\ReportRun the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of Report Runs, with the most recent appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Reporting\ReportRun> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing Report Run. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Reporting\ReportRun + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Reporting/ReportType.php b/libraries/Stripe/lib/Reporting/ReportType.php new file mode 100644 index 00000000000..a4a8ad84d99 --- /dev/null +++ b/libraries/Stripe/lib/Reporting/ReportType.php @@ -0,0 +1,67 @@ +API Access to Reports documentation + * for those Report Type IDs, along with required and optional parameters. + * + * Note that certain report types can only be run based on your live-mode data (not test-mode + * data), and will error when queried without a live-mode API key. + * + * @property string $id The ID of the Report Type, such as balance.summary.1. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $data_available_end Most recent time for which this Report Type is available. Measured in seconds since the Unix epoch. + * @property int $data_available_start Earliest time for which this Report Type is available. Measured in seconds since the Unix epoch. + * @property null|string[] $default_columns List of column names that are included by default when this Report Type gets run. (If the Report Type doesn't support the columns parameter, this will be null.) + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $name Human-readable name of the Report Type + * @property int $updated When this Report Type was latest updated. Measured in seconds since the Unix epoch. + * @property int $version Version of the Report Type. Different versions report with the same ID will have the same purpose, but may take different run parameters or have different result schemas. + */ +class ReportType extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'reporting.report_type'; + + /** + * Returns a full list of Report Types. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Reporting\ReportType> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a Report Type. (Certain report types require a live-mode API key.). + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Reporting\ReportType + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/RequestTelemetry.php b/libraries/Stripe/lib/RequestTelemetry.php new file mode 100644 index 00000000000..6a3ff92dd25 --- /dev/null +++ b/libraries/Stripe/lib/RequestTelemetry.php @@ -0,0 +1,32 @@ +requestId = $requestId; + $this->requestDuration = $requestDuration; + $this->usage = $usage; + } +} diff --git a/libraries/Stripe/lib/ReserveTransaction.php b/libraries/Stripe/lib/ReserveTransaction.php new file mode 100644 index 00000000000..94d376e569f --- /dev/null +++ b/libraries/Stripe/lib/ReserveTransaction.php @@ -0,0 +1,17 @@ +ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + */ +class ReserveTransaction extends ApiResource +{ + const OBJECT_NAME = 'reserve_transaction'; +} diff --git a/libraries/Stripe/lib/Review.php b/libraries/Stripe/lib/Review.php new file mode 100644 index 00000000000..2b17d8e50f1 --- /dev/null +++ b/libraries/Stripe/lib/Review.php @@ -0,0 +1,109 @@ +Radar and reviewing payments + * here. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $billing_zip The ZIP or postal code of the card used, if applicable. + * @property null|string|\EDD\Vendor\Stripe\Charge $charge The charge associated with this review. + * @property null|string $closed_reason The reason the review was closed, or null if it has not yet been closed. One of approved, refunded, refunded_as_fraud, disputed, or redacted. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $ip_address The IP address where the payment originated. + * @property null|\EDD\Vendor\Stripe\StripeObject $ip_address_location Information related to the location of the payment. Note that this information is an approximation and attempts to locate the nearest population center - it should not be used to determine a specific address. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property bool $open If true, the review needs action. + * @property string $opened_reason The reason the review was opened. One of rule or manual. + * @property null|string|\EDD\Vendor\Stripe\PaymentIntent $payment_intent The PaymentIntent ID associated with this review, if one exists. + * @property string $reason The reason the review is currently open or closed. One of rule, manual, approved, refunded, refunded_as_fraud, disputed, or redacted. + * @property null|\EDD\Vendor\Stripe\StripeObject $session Information related to the browsing session of the user who initiated the payment. + */ +class Review extends ApiResource +{ + const OBJECT_NAME = 'review'; + + const CLOSED_REASON_APPROVED = 'approved'; + const CLOSED_REASON_DISPUTED = 'disputed'; + const CLOSED_REASON_REDACTED = 'redacted'; + const CLOSED_REASON_REFUNDED = 'refunded'; + const CLOSED_REASON_REFUNDED_AS_FRAUD = 'refunded_as_fraud'; + + const OPENED_REASON_MANUAL = 'manual'; + const OPENED_REASON_RULE = 'rule'; + + /** + * Returns a list of Review objects that have open set to + * true. The objects are sorted in descending order by creation date, + * with the most recently created object appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Review> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a Review object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Review + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Possible string representations of the current, the opening or the closure reason of the review. + * Not all of these enumeration apply to all of the ´reason´ fields. Please consult the Review object to + * determine where these are apply. + * + * @see https://stripe.com/docs/api/radar/reviews/object + */ + const REASON_APPROVED = 'approved'; + const REASON_DISPUTED = 'disputed'; + const REASON_MANUAL = 'manual'; + const REASON_REFUNDED = 'refunded'; + const REASON_REFUNDED_AS_FRAUD = 'refunded_as_fraud'; + const REASON_RULE = 'rule'; + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Review the approved review + */ + public function approve($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/approve'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/SearchResult.php b/libraries/Stripe/lib/SearchResult.php new file mode 100644 index 00000000000..81c7e68cbd4 --- /dev/null +++ b/libraries/Stripe/lib/SearchResult.php @@ -0,0 +1,241 @@ +Collection in that they both wrap + * around a list of objects and provide pagination. However the + * SearchResult object paginates by relying on a + * next_page token included in the response rather than using + * object IDs and a starting_before/ending_after + * parameter. Thus, SearchResult only supports forwards pagination. + * + * The {@see $total_count} property is only available when + * the `expand` parameter contains `total_count`. + * + * @template TStripeObject of StripeObject + * @template-implements \IteratorAggregate + * + * @property string $object + * @property string $url + * @property string $next_page + * @property int $total_count + * @property bool $has_more + * @property TStripeObject[] $data + */ +class SearchResult extends StripeObject implements \Countable, \IteratorAggregate +{ + const OBJECT_NAME = 'search_result'; + + use ApiOperations\Request; + + /** @var array */ + protected $filters = []; + + /** + * @return string the base URL for the given class + */ + public static function baseUrl() + { + return Stripe::$apiBase; + } + + /** + * Returns the filters. + * + * @return array the filters + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Sets the filters, removing paging options. + * + * @param array $filters the filters + */ + public function setFilters($filters) + { + $this->filters = $filters; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($k) + { + if (\is_string($k)) { + return parent::offsetGet($k); + } + $msg = "You tried to access the {$k} index, but SearchResult " . + 'types only support string keys. (HINT: Search calls ' . + 'return an object with a `data` (which is the data ' . + "array). You likely want to call ->data[{$k}])"; + + throw new Exception\InvalidArgumentException($msg); + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws Exception\ApiErrorException + * + * @return SearchResult + */ + public function all($params = null, $opts = null) + { + self::_validateParams($params); + list($url, $params) = $this->extractPathAndUpdateParams($params); + + list($response, $opts) = $this->_request('get', $url, $params, $opts); + $obj = Util\Util::convertToStripeObject($response, $opts); + if (!($obj instanceof \EDD\Vendor\Stripe\SearchResult)) { + throw new \EDD\Vendor\Stripe\Exception\UnexpectedValueException( + 'Expected type ' . \EDD\Vendor\Stripe\SearchResult::class . ', got "' . \get_class($obj) . '" instead.' + ); + } + $obj->setFilters($params); + + return $obj; + } + + /** + * @return int the number of objects in the current page + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->data); + } + + /** + * @return \ArrayIterator an iterator that can be used to iterate + * across objects in the current page + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + /** + * @throws Exception\ApiErrorException + * + * @return \Generator|TStripeObject[] A generator that can be used to + * iterate across all objects across all pages. As page boundaries are + * encountered, the next page will be fetched automatically for + * continued iteration. + */ + public function autoPagingIterator() + { + $page = $this; + + while (true) { + foreach ($page as $item) { + yield $item; + } + $page = $page->nextPage(); + + if ($page->isEmpty()) { + break; + } + } + } + + /** + * Returns an empty set of search results. This is returned from + * {@see nextPage()} when we know that there isn't a next page in order to + * replicate the behavior of the API when it attempts to return a page + * beyond the last. + * + * @param null|array|string $opts + * + * @return SearchResult + */ + public static function emptySearchResult($opts = null) + { + return SearchResult::constructFrom(['data' => []], $opts); + } + + /** + * Returns true if the page object contains no element. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->data); + } + + /** + * Fetches the next page in the resource list (if there is one). + * + * This method will try to respect the limit of the current page. If none + * was given, the default limit will be fetched again. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws Exception\ApiErrorException + * + * @return SearchResult + */ + public function nextPage($params = null, $opts = null) + { + if (!$this->has_more) { + return static::emptySearchResult($opts); + } + + $params = \array_merge( + $this->filters ?: [], + ['page' => $this->next_page], + $params ?: [] + ); + + return $this->all($params, $opts); + } + + /** + * Gets the first item from the current page. Returns `null` if the current page is empty. + * + * @return null|TStripeObject + */ + public function first() + { + return \count($this->data) > 0 ? $this->data[0] : null; + } + + /** + * Gets the last item from the current page. Returns `null` if the current page is empty. + * + * @return null|TStripeObject + */ + public function last() + { + return \count($this->data) > 0 ? $this->data[\count($this->data) - 1] : null; + } + + private function extractPathAndUpdateParams($params) + { + $url = \parse_url($this->url); + + if (!isset($url['path'])) { + throw new Exception\UnexpectedValueException("Could not parse list url into parts: {$url}"); + } + + if (isset($url['query'])) { + // If the URL contains a query param, parse it out into $params so they + // don't interact weirdly with each other. + $query = []; + \parse_str($url['query'], $query); + $params = \array_merge($params ?: [], $query); + } + + return [$url['path'], $params]; + } +} diff --git a/libraries/Stripe/lib/Service/AbstractService.php b/libraries/Stripe/lib/Service/AbstractService.php new file mode 100644 index 00000000000..ea29693af02 --- /dev/null +++ b/libraries/Stripe/lib/Service/AbstractService.php @@ -0,0 +1,111 @@ +client = $client; + $this->streamingClient = $client; + } + + /** + * Gets the client used by this service to send requests. + * + * @return \EDD\Vendor\Stripe\StripeClientInterface + */ + public function getClient() + { + return $this->client; + } + + /** + * Gets the client used by this service to send requests. + * + * @return \EDD\Vendor\Stripe\StripeStreamingClientInterface + */ + public function getStreamingClient() + { + return $this->streamingClient; + } + + /** + * Translate null values to empty strings. For service methods, + * we interpret null as a request to unset the field, which + * corresponds to sending an empty string for the field to the + * API. + * + * @param null|array $params + */ + private static function formatParams($params) + { + if (null === $params) { + return null; + } + \array_walk_recursive($params, function (&$value, $key) { + if (null === $value) { + $value = ''; + } + }); + + return $params; + } + + protected function request($method, $path, $params, $opts) + { + return $this->getClient()->request($method, $path, self::formatParams($params), $opts); + } + + protected function requestStream($method, $path, $readBodyChunkCallable, $params, $opts) + { + // TODO (MAJOR): Add this method to StripeClientInterface + // @phpstan-ignore-next-line + return $this->getStreamingClient()->requestStream($method, $path, $readBodyChunkCallable, self::formatParams($params), $opts); + } + + protected function requestCollection($method, $path, $params, $opts) + { + // TODO (MAJOR): Add this method to StripeClientInterface + // @phpstan-ignore-next-line + return $this->getClient()->requestCollection($method, $path, self::formatParams($params), $opts); + } + + protected function requestSearchResult($method, $path, $params, $opts) + { + // TODO (MAJOR): Add this method to StripeClientInterface + // @phpstan-ignore-next-line + return $this->getClient()->requestSearchResult($method, $path, self::formatParams($params), $opts); + } + + protected function buildPath($basePath, ...$ids) + { + foreach ($ids as $id) { + if (null === $id || '' === \trim($id)) { + $msg = 'The resource ID cannot be null or whitespace.'; + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException($msg); + } + } + + return \sprintf($basePath, ...\array_map('\urlencode', $ids)); + } +} diff --git a/libraries/Stripe/lib/Service/AbstractServiceFactory.php b/libraries/Stripe/lib/Service/AbstractServiceFactory.php new file mode 100644 index 00000000000..81a0a5cbccd --- /dev/null +++ b/libraries/Stripe/lib/Service/AbstractServiceFactory.php @@ -0,0 +1,69 @@ + */ + private $services; + + /** + * @param \EDD\Vendor\Stripe\StripeClientInterface $client + */ + public function __construct($client) + { + $this->client = $client; + $this->services = []; + } + + /** + * @param string $name + * + * @return null|string + */ + abstract protected function getServiceClass($name); + + /** + * @param string $name + * + * @return null|AbstractService|AbstractServiceFactory + */ + public function __get($name) + { + return $this->getService($name); + } + + /** + * @param string $name + * + * @return null|AbstractService|AbstractServiceFactory + */ + public function getService($name) + { + $serviceClass = $this->getServiceClass($name); + if (null !== $serviceClass) { + if (!\array_key_exists($name, $this->services)) { + $this->services[$name] = new $serviceClass($this->client); + } + + return $this->services[$name]; + } + + \trigger_error('Undefined property: ' . static::class . '::$' . $name); + + return null; + } +} diff --git a/libraries/Stripe/lib/Service/AccountLinkService.php b/libraries/Stripe/lib/Service/AccountLinkService.php new file mode 100644 index 00000000000..e00fe925d5c --- /dev/null +++ b/libraries/Stripe/lib/Service/AccountLinkService.php @@ -0,0 +1,29 @@ +request('post', '/v1/account_links', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/AccountService.php b/libraries/Stripe/lib/Service/AccountService.php new file mode 100644 index 00000000000..4aa7a47e562 --- /dev/null +++ b/libraries/Stripe/lib/Service/AccountService.php @@ -0,0 +1,412 @@ +Connect. If you’re not a platform, the list is empty. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Account> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/accounts', $params, $opts); + } + + /** + * Returns a list of capabilities associated with the account. The capabilities are + * returned sorted by creation date, with the most recent capability appearing + * first. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Capability> + */ + public function allCapabilities($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/accounts/%s/capabilities', $parentId), $params, $opts); + } + + /** + * List external accounts for an account. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card> + */ + public function allExternalAccounts($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/accounts/%s/external_accounts', $parentId), $params, $opts); + } + + /** + * Returns a list of people associated with the account’s legal entity. The people + * are returned sorted by creation date, with the most recent people appearing + * first. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Person> + */ + public function allPersons($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/accounts/%s/persons', $parentId), $params, $opts); + } + + /** + * With Connect, you can create EDD\Vendor\Stripe accounts for + * your users. To do this, you’ll first need to register your + * platform. + * + * If you’ve already collected information for your connected accounts, you can prefill that information + * when creating the account. Connect Onboarding won’t ask for the prefilled + * information during account onboarding. You can prefill any information on the + * account. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/accounts', $params, $opts); + } + + /** + * Create an external account for a given account. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card + */ + public function createExternalAccount($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/accounts/%s/external_accounts', $parentId), $params, $opts); + } + + /** + * Creates a single-use login link for a connected account to access the Express + * Dashboard. + * + * You can only create login links for accounts that use the Express Dashboard and are connected to + * your platform. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\LoginLink + */ + public function createLoginLink($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/accounts/%s/login_links', $parentId), $params, $opts); + } + + /** + * Creates a new person. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Person + */ + public function createPerson($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/accounts/%s/persons', $parentId), $params, $opts); + } + + /** + * With Connect, you can delete accounts you manage. + * + * Test-mode accounts can be deleted at any time. + * + * Live-mode accounts where EDD\Vendor\Stripe is responsible for negative account balances + * cannot be deleted, which includes Standard accounts. Live-mode accounts where + * your platform is liable for negative account balances, which includes Custom and + * Express accounts, can be deleted when all balances are zero. + * + * If you want to delete your own account, use the account information tab in + * your account settings instead. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/accounts/%s', $id), $params, $opts); + } + + /** + * Delete a specified external account for a given account. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card + */ + public function deleteExternalAccount($parentId, $id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/accounts/%s/external_accounts/%s', $parentId, $id), $params, $opts); + } + + /** + * Deletes an existing person’s relationship to the account’s legal entity. Any + * person with a relationship for an account can be deleted through the API, except + * if the person is the account_opener. If your integration is using + * the executive parameter, you cannot delete the only verified + * executive on file. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Person + */ + public function deletePerson($parentId, $id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/accounts/%s/persons/%s', $parentId, $id), $params, $opts); + } + + /** + * With Connect, you can reject accounts that you have + * flagged as suspicious. + * + * Only accounts where your platform is liable for negative account balances, which + * includes Custom and Express accounts, can be rejected. Test-mode accounts can be + * rejected at any time. Live-mode accounts can only be rejected after all balances + * are zero. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account + */ + public function reject($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/accounts/%s/reject', $id), $params, $opts); + } + + /** + * Retrieves information about the specified Account Capability. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Capability + */ + public function retrieveCapability($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/accounts/%s/capabilities/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieve a specified external account for a given account. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card + */ + public function retrieveExternalAccount($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/accounts/%s/external_accounts/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieves an existing person. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Person + */ + public function retrievePerson($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/accounts/%s/persons/%s', $parentId, $id), $params, $opts); + } + + /** + * Updates a connected account by setting the + * values of the parameters passed. Any parameters not provided are left unchanged. + * + * For accounts where controller.requirement_collection + * is application, which includes Custom accounts, you can update any + * information on the account. + * + * For accounts where controller.requirement_collection + * is stripe, which includes Standard and Express accounts, you can + * update all information until you create an Account + * Link or Account Session to start Connect + * onboarding, after which some properties can no longer be updated. + * + * To update your own account, use the Dashboard. Refer to our + * Connect documentation to learn + * more about updating accounts. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/accounts/%s', $id), $params, $opts); + } + + /** + * Updates an existing Account Capability. Request or remove a capability by + * updating its requested parameter. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Capability + */ + public function updateCapability($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/accounts/%s/capabilities/%s', $parentId, $id), $params, $opts); + } + + /** + * Updates the metadata, account holder name, account holder type of a bank account + * belonging to a connected account and optionally sets it as the default for its + * currency. Other bank account details are not editable by design. + * + * You can only update bank accounts when account.controller.requirement_collection + * is application, which includes Custom accounts. + * + * You can re-enable a disabled bank account by performing an update call without + * providing any arguments or changes. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card + */ + public function updateExternalAccount($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/accounts/%s/external_accounts/%s', $parentId, $id), $params, $opts); + } + + /** + * Updates an existing person. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Person + */ + public function updatePerson($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/accounts/%s/persons/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieves the details of an account. + * + * @param null|string $id + * @param null|array $params + * @param null|array|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account + */ + public function retrieve($id = null, $params = null, $opts = null) + { + if (null === $id) { + return $this->request('get', '/v1/account', $params, $opts); + } + + return $this->request('get', $this->buildPath('/v1/accounts/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/AccountSessionService.php b/libraries/Stripe/lib/Service/AccountSessionService.php new file mode 100644 index 00000000000..496e620a5bf --- /dev/null +++ b/libraries/Stripe/lib/Service/AccountSessionService.php @@ -0,0 +1,28 @@ +request('post', '/v1/account_sessions', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/ApplePayDomainService.php b/libraries/Stripe/lib/Service/ApplePayDomainService.php new file mode 100644 index 00000000000..57490d9ba70 --- /dev/null +++ b/libraries/Stripe/lib/Service/ApplePayDomainService.php @@ -0,0 +1,74 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/apple_pay/domains', $params, $opts); + } + + /** + * Create an apple pay domain. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplePayDomain + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/apple_pay/domains', $params, $opts); + } + + /** + * Delete an apple pay domain. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplePayDomain + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/apple_pay/domains/%s', $id), $params, $opts); + } + + /** + * Retrieve an apple pay domain. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplePayDomain + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/apple_pay/domains/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/ApplicationFeeService.php b/libraries/Stripe/lib/Service/ApplicationFeeService.php new file mode 100644 index 00000000000..f8d1ed04567 --- /dev/null +++ b/libraries/Stripe/lib/Service/ApplicationFeeService.php @@ -0,0 +1,129 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/application_fees', $params, $opts); + } + + /** + * You can see a list of the refunds belonging to a specific application fee. Note + * that the 10 most recent refunds are always available by default on the + * application fee object. If you need more than those 10, you can use this API + * method and the limit and starting_after parameters to + * page through additional refunds. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ApplicationFeeRefund> + */ + public function allRefunds($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/application_fees/%s/refunds', $parentId), $params, $opts); + } + + /** + * Refunds an application fee that has previously been collected but not yet + * refunded. Funds will be refunded to the EDD\Vendor\Stripe account from which the fee was + * originally collected. + * + * You can optionally refund only part of an application fee. You can do so + * multiple times, until the entire fee has been refunded. + * + * Once entirely refunded, an application fee can’t be refunded again. This method + * will raise an error when called on an already-refunded application fee, or when + * trying to refund more money than is left on an application fee. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplicationFeeRefund + */ + public function createRefund($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/application_fees/%s/refunds', $parentId), $params, $opts); + } + + /** + * Retrieves the details of an application fee that your account has collected. The + * same information is returned when refunding the application fee. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplicationFee + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/application_fees/%s', $id), $params, $opts); + } + + /** + * By default, you can see the 10 most recent refunds stored directly on the + * application fee object, but you can also retrieve details about a specific + * refund stored on the application fee. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplicationFeeRefund + */ + public function retrieveRefund($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/application_fees/%s/refunds/%s', $parentId, $id), $params, $opts); + } + + /** + * Updates the specified application fee refund by setting the values of the + * parameters passed. Any parameters not provided will be left unchanged. + * + * This request only accepts metadata as an argument. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ApplicationFeeRefund + */ + public function updateRefund($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/application_fees/%s/refunds/%s', $parentId, $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Apps/AppsServiceFactory.php b/libraries/Stripe/lib/Service/Apps/AppsServiceFactory.php new file mode 100644 index 00000000000..83f085aa318 --- /dev/null +++ b/libraries/Stripe/lib/Service/Apps/AppsServiceFactory.php @@ -0,0 +1,25 @@ + + */ + private static $classMap = [ + 'secrets' => SecretService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Apps/SecretService.php b/libraries/Stripe/lib/Service/Apps/SecretService.php new file mode 100644 index 00000000000..6c76cfae7e9 --- /dev/null +++ b/libraries/Stripe/lib/Service/Apps/SecretService.php @@ -0,0 +1,72 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/apps/secrets', $params, $opts); + } + + /** + * Create or replace a secret in the secret store. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Apps\Secret + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/apps/secrets', $params, $opts); + } + + /** + * Deletes a secret from the secret store by name and scope. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Apps\Secret + */ + public function deleteWhere($params = null, $opts = null) + { + return $this->request('post', '/v1/apps/secrets/delete', $params, $opts); + } + + /** + * Finds a secret in the secret store by name and scope. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Apps\Secret + */ + public function find($params = null, $opts = null) + { + return $this->request('get', '/v1/apps/secrets/find', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/BalanceService.php b/libraries/Stripe/lib/Service/BalanceService.php new file mode 100644 index 00000000000..3e9ebb6f1c1 --- /dev/null +++ b/libraries/Stripe/lib/Service/BalanceService.php @@ -0,0 +1,30 @@ +Accounting + * for negative balances. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Balance + */ + public function retrieve($params = null, $opts = null) + { + return $this->request('get', '/v1/balance', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/BalanceTransactionService.php b/libraries/Stripe/lib/Service/BalanceTransactionService.php new file mode 100644 index 00000000000..5519ef066b8 --- /dev/null +++ b/libraries/Stripe/lib/Service/BalanceTransactionService.php @@ -0,0 +1,51 @@ +/v1/balance/history. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\BalanceTransaction> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/balance_transactions', $params, $opts); + } + + /** + * Retrieves the balance transaction with the given ID. + * + * Note that this endpoint previously used the path + * /v1/balance/history/:id. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BalanceTransaction + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/balance_transactions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Billing/AlertService.php b/libraries/Stripe/lib/Service/Billing/AlertService.php new file mode 100644 index 00000000000..e3a7e4ee985 --- /dev/null +++ b/libraries/Stripe/lib/Service/Billing/AlertService.php @@ -0,0 +1,107 @@ +request('post', $this->buildPath('/v1/billing/alerts/%s/activate', $id), $params, $opts); + } + + /** + * Lists billing active and inactive alerts. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Billing\Alert> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/billing/alerts', $params, $opts); + } + + /** + * Archives this alert, removing it from the list view and APIs. This is + * non-reversible. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert + */ + public function archive($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/billing/alerts/%s/archive', $id), $params, $opts); + } + + /** + * Creates a billing alert. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/billing/alerts', $params, $opts); + } + + /** + * Deactivates this alert, preventing it from triggering. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert + */ + public function deactivate($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/billing/alerts/%s/deactivate', $id), $params, $opts); + } + + /** + * Retrieves a billing alert given an ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Alert + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/billing/alerts/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Billing/BillingServiceFactory.php b/libraries/Stripe/lib/Service/Billing/BillingServiceFactory.php new file mode 100644 index 00000000000..fae95863c60 --- /dev/null +++ b/libraries/Stripe/lib/Service/Billing/BillingServiceFactory.php @@ -0,0 +1,31 @@ + + */ + private static $classMap = [ + 'alerts' => AlertService::class, + 'meterEventAdjustments' => MeterEventAdjustmentService::class, + 'meterEvents' => MeterEventService::class, + 'meters' => MeterService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Billing/MeterEventAdjustmentService.php b/libraries/Stripe/lib/Service/Billing/MeterEventAdjustmentService.php new file mode 100644 index 00000000000..f8af4e30660 --- /dev/null +++ b/libraries/Stripe/lib/Service/Billing/MeterEventAdjustmentService.php @@ -0,0 +1,27 @@ +request('post', '/v1/billing/meter_event_adjustments', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Billing/MeterEventService.php b/libraries/Stripe/lib/Service/Billing/MeterEventService.php new file mode 100644 index 00000000000..8981fa6bc29 --- /dev/null +++ b/libraries/Stripe/lib/Service/Billing/MeterEventService.php @@ -0,0 +1,27 @@ +request('post', '/v1/billing/meter_events', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Billing/MeterService.php b/libraries/Stripe/lib/Service/Billing/MeterService.php new file mode 100644 index 00000000000..d36f6083add --- /dev/null +++ b/libraries/Stripe/lib/Service/Billing/MeterService.php @@ -0,0 +1,122 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/billing/meters', $params, $opts); + } + + /** + * Retrieve a list of billing meter event summaries. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Billing\MeterEventSummary> + */ + public function allEventSummaries($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/billing/meters/%s/event_summaries', $parentId), $params, $opts); + } + + /** + * Creates a billing meter. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/billing/meters', $params, $opts); + } + + /** + * Deactivates a billing meter. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter + */ + public function deactivate($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/billing/meters/%s/deactivate', $id), $params, $opts); + } + + /** + * Reactivates a billing meter. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter + */ + public function reactivate($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/billing/meters/%s/reactivate', $id), $params, $opts); + } + + /** + * Retrieves a billing meter given an ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/billing/meters/%s', $id), $params, $opts); + } + + /** + * Updates a billing meter. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Billing\Meter + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/billing/meters/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/BillingPortal/BillingPortalServiceFactory.php b/libraries/Stripe/lib/Service/BillingPortal/BillingPortalServiceFactory.php new file mode 100644 index 00000000000..35910d57f43 --- /dev/null +++ b/libraries/Stripe/lib/Service/BillingPortal/BillingPortalServiceFactory.php @@ -0,0 +1,27 @@ + + */ + private static $classMap = [ + 'configurations' => ConfigurationService::class, + 'sessions' => SessionService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/BillingPortal/ConfigurationService.php b/libraries/Stripe/lib/Service/BillingPortal/ConfigurationService.php new file mode 100644 index 00000000000..5de5c147204 --- /dev/null +++ b/libraries/Stripe/lib/Service/BillingPortal/ConfigurationService.php @@ -0,0 +1,77 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/billing_portal/configurations', $params, $opts); + } + + /** + * Creates a configuration that describes the functionality and behavior of a + * PortalSession. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BillingPortal\Configuration + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/billing_portal/configurations', $params, $opts); + } + + /** + * Retrieves a configuration that describes the functionality of the customer + * portal. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BillingPortal\Configuration + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/billing_portal/configurations/%s', $id), $params, $opts); + } + + /** + * Updates a configuration that describes the functionality of the customer portal. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\BillingPortal\Configuration + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/billing_portal/configurations/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/BillingPortal/SessionService.php b/libraries/Stripe/lib/Service/BillingPortal/SessionService.php new file mode 100644 index 00000000000..1ecabe05c41 --- /dev/null +++ b/libraries/Stripe/lib/Service/BillingPortal/SessionService.php @@ -0,0 +1,27 @@ +request('post', '/v1/billing_portal/sessions', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/ChargeService.php b/libraries/Stripe/lib/Service/ChargeService.php new file mode 100644 index 00000000000..cf1332256cb --- /dev/null +++ b/libraries/Stripe/lib/Service/ChargeService.php @@ -0,0 +1,126 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/charges', $params, $opts); + } + + /** + * Capture the payment of an existing, uncaptured charge that was created with the + * capture option set to false. + * + * Uncaptured payments expire a set number of days after they are created (7 by default), after which they are + * marked as refunded and capture attempts will fail. + * + * Don’t use this method to capture a PaymentIntent-initiated charge. Use Capture a PaymentIntent. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Charge + */ + public function capture($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/charges/%s/capture', $id), $params, $opts); + } + + /** + * This method is no longer recommended—use the Payment Intents API to initiate a new + * payment instead. Confirmation of the PaymentIntent creates the + * Charge object used to request payment. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Charge + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/charges', $params, $opts); + } + + /** + * Retrieves the details of a charge that has previously been created. Supply the + * unique charge ID that was returned from your previous request, and EDD\Vendor\Stripe will + * return the corresponding charge information. The same information is returned + * when creating or refunding the charge. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Charge + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/charges/%s', $id), $params, $opts); + } + + /** + * Search for charges you’ve previously created using Stripe’s Search Query Language. Don’t use + * search in read-after-write flows where strict consistency is necessary. Under + * normal operating conditions, data is searchable in less than a minute. + * Occasionally, propagation of new or updated data can be up to an hour behind + * during outages. Search functionality is not available to merchants in India. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Charge> + */ + public function search($params = null, $opts = null) + { + return $this->requestSearchResult('get', '/v1/charges/search', $params, $opts); + } + + /** + * Updates the specified charge by setting the values of the parameters passed. Any + * parameters not provided will be left unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Charge + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/charges/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Checkout/CheckoutServiceFactory.php b/libraries/Stripe/lib/Service/Checkout/CheckoutServiceFactory.php new file mode 100644 index 00000000000..e885efb26ca --- /dev/null +++ b/libraries/Stripe/lib/Service/Checkout/CheckoutServiceFactory.php @@ -0,0 +1,25 @@ + + */ + private static $classMap = [ + 'sessions' => SessionService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Checkout/SessionService.php b/libraries/Stripe/lib/Service/Checkout/SessionService.php new file mode 100644 index 00000000000..561372757f0 --- /dev/null +++ b/libraries/Stripe/lib/Service/Checkout/SessionService.php @@ -0,0 +1,112 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/checkout/sessions', $params, $opts); + } + + /** + * When retrieving a Checkout Session, there is an includable + * line_items property containing the first handful of those + * items. There is also a URL where you can retrieve the full (paginated) list of + * line items. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> + */ + public function allLineItems($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/checkout/sessions/%s/line_items', $id), $params, $opts); + } + + /** + * Creates a Session object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Checkout\Session + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/checkout/sessions', $params, $opts); + } + + /** + * A Session can be expired when it is in one of these statuses: open. + * + * After it expires, a customer can’t complete a Session and customers loading the + * Session see a message saying the Session is expired. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Checkout\Session + */ + public function expire($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/checkout/sessions/%s/expire', $id), $params, $opts); + } + + /** + * Retrieves a Session object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Checkout\Session + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/checkout/sessions/%s', $id), $params, $opts); + } + + /** + * Updates a Session object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Checkout\Session + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/checkout/sessions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Climate/ClimateServiceFactory.php b/libraries/Stripe/lib/Service/Climate/ClimateServiceFactory.php new file mode 100644 index 00000000000..78b5049b072 --- /dev/null +++ b/libraries/Stripe/lib/Service/Climate/ClimateServiceFactory.php @@ -0,0 +1,29 @@ + + */ + private static $classMap = [ + 'orders' => OrderService::class, + 'products' => ProductService::class, + 'suppliers' => SupplierService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Climate/OrderService.php b/libraries/Stripe/lib/Service/Climate/OrderService.php new file mode 100644 index 00000000000..468ae31bd17 --- /dev/null +++ b/libraries/Stripe/lib/Service/Climate/OrderService.php @@ -0,0 +1,98 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/climate/orders', $params, $opts); + } + + /** + * Cancels a Climate order. You can cancel an order within 24 hours of creation. + * EDD\Vendor\Stripe refunds the reservation amount_subtotal, but not the + * amount_fees for user-triggered cancellations. Frontier might cancel + * reservations if suppliers fail to deliver. If Frontier cancels the reservation, + * EDD\Vendor\Stripe provides 90 days advance notice and refunds the + * amount_total. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Order + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/climate/orders/%s/cancel', $id), $params, $opts); + } + + /** + * Creates a Climate order object for a given Climate product. The order will be + * processed immediately after creation and payment will be deducted your EDD\Vendor\Stripe + * balance. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Order + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/climate/orders', $params, $opts); + } + + /** + * Retrieves the details of a Climate order object with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Order + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/climate/orders/%s', $id), $params, $opts); + } + + /** + * Updates the specified order by setting the values of the parameters passed. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Order + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/climate/orders/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Climate/ProductService.php b/libraries/Stripe/lib/Service/Climate/ProductService.php new file mode 100644 index 00000000000..a3c421f98f3 --- /dev/null +++ b/libraries/Stripe/lib/Service/Climate/ProductService.php @@ -0,0 +1,43 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/climate/products', $params, $opts); + } + + /** + * Retrieves the details of a Climate product with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Product + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/climate/products/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Climate/SupplierService.php b/libraries/Stripe/lib/Service/Climate/SupplierService.php new file mode 100644 index 00000000000..263957839f1 --- /dev/null +++ b/libraries/Stripe/lib/Service/Climate/SupplierService.php @@ -0,0 +1,43 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/climate/suppliers', $params, $opts); + } + + /** + * Retrieves a Climate supplier object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Climate\Supplier + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/climate/suppliers/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/ConfirmationTokenService.php b/libraries/Stripe/lib/Service/ConfirmationTokenService.php new file mode 100644 index 00000000000..d822b751996 --- /dev/null +++ b/libraries/Stripe/lib/Service/ConfirmationTokenService.php @@ -0,0 +1,28 @@ +request('get', $this->buildPath('/v1/confirmation_tokens/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/CoreServiceFactory.php b/libraries/Stripe/lib/Service/CoreServiceFactory.php new file mode 100644 index 00000000000..e8d848d7dea --- /dev/null +++ b/libraries/Stripe/lib/Service/CoreServiceFactory.php @@ -0,0 +1,163 @@ + + */ + private static $classMap = [ + 'oauth' => OAuthService::class, + // Class Map: The beginning of the section generated from our OpenAPI spec + 'accountLinks' => AccountLinkService::class, + 'accounts' => AccountService::class, + 'accountSessions' => AccountSessionService::class, + 'applePayDomains' => ApplePayDomainService::class, + 'applicationFees' => ApplicationFeeService::class, + 'apps' => Apps\AppsServiceFactory::class, + 'balance' => BalanceService::class, + 'balanceTransactions' => BalanceTransactionService::class, + 'billing' => Billing\BillingServiceFactory::class, + 'billingPortal' => BillingPortal\BillingPortalServiceFactory::class, + 'charges' => ChargeService::class, + 'checkout' => Checkout\CheckoutServiceFactory::class, + 'climate' => Climate\ClimateServiceFactory::class, + 'confirmationTokens' => ConfirmationTokenService::class, + 'countrySpecs' => CountrySpecService::class, + 'coupons' => CouponService::class, + 'creditNotes' => CreditNoteService::class, + 'customers' => CustomerService::class, + 'customerSessions' => CustomerSessionService::class, + 'disputes' => DisputeService::class, + 'entitlements' => Entitlements\EntitlementsServiceFactory::class, + 'ephemeralKeys' => EphemeralKeyService::class, + 'events' => EventService::class, + 'exchangeRates' => ExchangeRateService::class, + 'fileLinks' => FileLinkService::class, + 'files' => FileService::class, + 'financialConnections' => FinancialConnections\FinancialConnectionsServiceFactory::class, + 'forwarding' => Forwarding\ForwardingServiceFactory::class, + 'identity' => Identity\IdentityServiceFactory::class, + 'invoiceItems' => InvoiceItemService::class, + 'invoiceRenderingTemplates' => InvoiceRenderingTemplateService::class, + 'invoices' => InvoiceService::class, + 'issuing' => Issuing\IssuingServiceFactory::class, + 'mandates' => MandateService::class, + 'paymentIntents' => PaymentIntentService::class, + 'paymentLinks' => PaymentLinkService::class, + 'paymentMethodConfigurations' => PaymentMethodConfigurationService::class, + 'paymentMethodDomains' => PaymentMethodDomainService::class, + 'paymentMethods' => PaymentMethodService::class, + 'payouts' => PayoutService::class, + 'plans' => PlanService::class, + 'prices' => PriceService::class, + 'products' => ProductService::class, + 'promotionCodes' => PromotionCodeService::class, + 'quotes' => QuoteService::class, + 'radar' => Radar\RadarServiceFactory::class, + 'refunds' => RefundService::class, + 'reporting' => Reporting\ReportingServiceFactory::class, + 'reviews' => ReviewService::class, + 'setupAttempts' => SetupAttemptService::class, + 'setupIntents' => SetupIntentService::class, + 'shippingRates' => ShippingRateService::class, + 'sigma' => Sigma\SigmaServiceFactory::class, + 'sources' => SourceService::class, + 'subscriptionItems' => SubscriptionItemService::class, + 'subscriptions' => SubscriptionService::class, + 'subscriptionSchedules' => SubscriptionScheduleService::class, + 'tax' => Tax\TaxServiceFactory::class, + 'taxCodes' => TaxCodeService::class, + 'taxIds' => TaxIdService::class, + 'taxRates' => TaxRateService::class, + 'terminal' => Terminal\TerminalServiceFactory::class, + 'testHelpers' => TestHelpers\TestHelpersServiceFactory::class, + 'tokens' => TokenService::class, + 'topups' => TopupService::class, + 'transfers' => TransferService::class, + 'treasury' => Treasury\TreasuryServiceFactory::class, + 'webhookEndpoints' => WebhookEndpointService::class, + // Class Map: The end of the section generated from our OpenAPI spec + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/CountrySpecService.php b/libraries/Stripe/lib/Service/CountrySpecService.php new file mode 100644 index 00000000000..bdb7c4a7f69 --- /dev/null +++ b/libraries/Stripe/lib/Service/CountrySpecService.php @@ -0,0 +1,43 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/country_specs', $params, $opts); + } + + /** + * Returns a Country Spec for a given Country code. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CountrySpec + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/country_specs/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/CouponService.php b/libraries/Stripe/lib/Service/CouponService.php new file mode 100644 index 00000000000..78aab18a7e9 --- /dev/null +++ b/libraries/Stripe/lib/Service/CouponService.php @@ -0,0 +1,108 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/coupons', $params, $opts); + } + + /** + * You can create coupons easily via the coupon management page of the + * EDD\Vendor\Stripe dashboard. Coupon creation is also accessible via the API if you need to + * create coupons on the fly. + * + * A coupon has either a percent_off or an amount_off and + * currency. If you set an amount_off, that amount will + * be subtracted from any invoice’s subtotal. For example, an invoice with a + * subtotal of 100 will have a final total of + * 0 if a coupon with an amount_off of + * 200 is applied to it and an invoice with a subtotal of + * 300 will have a final total of 100 if + * a coupon with an amount_off of 200 is applied to + * it. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Coupon + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/coupons', $params, $opts); + } + + /** + * You can delete coupons via the coupon management page of the + * EDD\Vendor\Stripe dashboard. However, deleting a coupon does not affect any customers who + * have already applied the coupon; it means that new customers can’t redeem the + * coupon. You can also delete coupons via the API. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Coupon + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/coupons/%s', $id), $params, $opts); + } + + /** + * Retrieves the coupon with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Coupon + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/coupons/%s', $id), $params, $opts); + } + + /** + * Updates the metadata of a coupon. Other coupon details (currency, duration, + * amount_off) are, by design, not editable. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Coupon + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/coupons/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/CreditNoteService.php b/libraries/Stripe/lib/Service/CreditNoteService.php new file mode 100644 index 00000000000..f4fcd266083 --- /dev/null +++ b/libraries/Stripe/lib/Service/CreditNoteService.php @@ -0,0 +1,160 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/credit_notes', $params, $opts); + } + + /** + * When retrieving a credit note, you’ll get a lines property + * containing the first handful of those items. There is also a URL where you can + * retrieve the full (paginated) list of line items. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CreditNoteLineItem> + */ + public function allLines($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/credit_notes/%s/lines', $parentId), $params, $opts); + } + + /** + * Issue a credit note to adjust the amount of a finalized invoice. For a + * status=open invoice, a credit note reduces its + * amount_due. For a status=paid invoice, a credit note + * does not affect its amount_due. Instead, it can result in any + * combination of the following:. + * + *
    • Refund: create a new refund (using refund_amount) or link + * an existing refund (using refund).
    • Customer balance + * credit: credit the customer’s balance (using credit_amount) which + * will be automatically applied to their next invoice when it’s finalized.
    • + *
    • Outside of EDD\Vendor\Stripe credit: record the amount that is or will be credited + * outside of EDD\Vendor\Stripe (using out_of_band_amount).
    + * + * For post-payment credit notes the sum of the refund, credit and outside of + * EDD\Vendor\Stripe amounts must equal the credit note total. + * + * You may issue multiple credit notes for an invoice. Each credit note will + * increment the invoice’s pre_payment_credit_notes_amount or + * post_payment_credit_notes_amount depending on its + * status at the time of credit note creation. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/credit_notes', $params, $opts); + } + + /** + * Get a preview of a credit note without creating it. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote + */ + public function preview($params = null, $opts = null) + { + return $this->request('get', '/v1/credit_notes/preview', $params, $opts); + } + + /** + * When retrieving a credit note preview, you’ll get a lines + * property containing the first handful of those items. This URL you can retrieve + * the full (paginated) list of line items. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CreditNoteLineItem> + */ + public function previewLines($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/credit_notes/preview/lines', $params, $opts); + } + + /** + * Retrieves the credit note object with the given identifier. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/credit_notes/%s', $id), $params, $opts); + } + + /** + * Updates an existing credit note. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/credit_notes/%s', $id), $params, $opts); + } + + /** + * Marks a credit note as void. Learn more about voiding credit notes. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CreditNote + */ + public function voidCreditNote($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/credit_notes/%s/void', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/CustomerService.php b/libraries/Stripe/lib/Service/CustomerService.php new file mode 100644 index 00000000000..cf408be2559 --- /dev/null +++ b/libraries/Stripe/lib/Service/CustomerService.php @@ -0,0 +1,502 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/customers', $params, $opts); + } + + /** + * Returns a list of transactions that updated the customer’s balances. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CustomerBalanceTransaction> + */ + public function allBalanceTransactions($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/customers/%s/balance_transactions', $parentId), $params, $opts); + } + + /** + * Returns a list of transactions that modified the customer’s cash balance. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\CustomerCashBalanceTransaction> + */ + public function allCashBalanceTransactions($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/customers/%s/cash_balance_transactions', $parentId), $params, $opts); + } + + /** + * Returns a list of PaymentMethods for a given Customer. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PaymentMethod> + */ + public function allPaymentMethods($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/customers/%s/payment_methods', $id), $params, $opts); + } + + /** + * List sources for a specified customer. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source> + */ + public function allSources($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/customers/%s/sources', $parentId), $params, $opts); + } + + /** + * Returns a list of tax IDs for a customer. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TaxId> + */ + public function allTaxIds($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/customers/%s/tax_ids', $parentId), $params, $opts); + } + + /** + * Creates a new customer object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Customer + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/customers', $params, $opts); + } + + /** + * Creates an immutable transaction that updates the customer’s credit balance. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerBalanceTransaction + */ + public function createBalanceTransaction($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s/balance_transactions', $parentId), $params, $opts); + } + + /** + * Retrieve funding instructions for a customer cash balance. If funding + * instructions do not yet exist for the customer, new funding instructions will be + * created. If funding instructions have already been created for a given customer, + * the same funding instructions will be retrieved. In other words, we will return + * the same funding instructions each time. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FundingInstructions + */ + public function createFundingInstructions($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s/funding_instructions', $id), $params, $opts); + } + + /** + * When you create a new credit card, you must specify a customer or recipient on + * which to create it. + * + * If the card’s owner has no default card, then the new card will become the + * default. However, if the owner already has a default, then it will not change. + * To change the default, you should update the + * customer to have a new default_source. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public function createSource($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s/sources', $parentId), $params, $opts); + } + + /** + * Creates a new tax_id object for a customer. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public function createTaxId($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s/tax_ids', $parentId), $params, $opts); + } + + /** + * Permanently deletes a customer. It cannot be undone. Also immediately cancels + * any active subscriptions on the customer. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Customer + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/customers/%s', $id), $params, $opts); + } + + /** + * Removes the currently applied discount on a customer. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Discount + */ + public function deleteDiscount($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/customers/%s/discount', $id), $params, $opts); + } + + /** + * Delete a specified source for a given customer. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public function deleteSource($parentId, $id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/customers/%s/sources/%s', $parentId, $id), $params, $opts); + } + + /** + * Deletes an existing tax_id object. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public function deleteTaxId($parentId, $id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/customers/%s/tax_ids/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieves a Customer object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Customer + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/customers/%s', $id), $params, $opts); + } + + /** + * Retrieves a specific customer balance transaction that updated the customer’s balances. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerBalanceTransaction + */ + public function retrieveBalanceTransaction($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/customers/%s/balance_transactions/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieves a customer’s cash balance. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CashBalance + */ + public function retrieveCashBalance($parentId, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/customers/%s/cash_balance', $parentId), $params, $opts); + } + + /** + * Retrieves a specific cash balance transaction, which updated the customer’s cash balance. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerCashBalanceTransaction + */ + public function retrieveCashBalanceTransaction($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/customers/%s/cash_balance_transactions/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieves a PaymentMethod object for a given Customer. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod + */ + public function retrievePaymentMethod($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/customers/%s/payment_methods/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieve a specified source for a given customer. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public function retrieveSource($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/customers/%s/sources/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieves the tax_id object with the given identifier. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public function retrieveTaxId($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/customers/%s/tax_ids/%s', $parentId, $id), $params, $opts); + } + + /** + * Search for customers you’ve previously created using Stripe’s Search Query Language. Don’t use + * search in read-after-write flows where strict consistency is necessary. Under + * normal operating conditions, data is searchable in less than a minute. + * Occasionally, propagation of new or updated data can be up to an hour behind + * during outages. Search functionality is not available to merchants in India. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Customer> + */ + public function search($params = null, $opts = null) + { + return $this->requestSearchResult('get', '/v1/customers/search', $params, $opts); + } + + /** + * Updates the specified customer by setting the values of the parameters passed. + * Any parameters not provided will be left unchanged. For example, if you pass the + * source parameter, that becomes the customer’s active source + * (e.g., a card) to be used for all charges in the future. When you update a + * customer to a new valid card source by passing the source + * parameter: for each of the customer’s current subscriptions, if the subscription + * bills automatically and is in the past_due state, then the latest + * open invoice for the subscription with automatic collection enabled will be + * retried. This retry will not count as an automatic retry, and will not affect + * the next regularly scheduled payment for the invoice. Changing the + * default_source for a customer will not trigger this behavior. + * + * This request accepts mostly the same arguments as the customer creation call. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Customer + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s', $id), $params, $opts); + } + + /** + * Most credit balance transaction fields are immutable, but you may update its + * description and metadata. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CustomerBalanceTransaction + */ + public function updateBalanceTransaction($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s/balance_transactions/%s', $parentId, $id), $params, $opts); + } + + /** + * Changes the settings on a customer’s cash balance. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\CashBalance + */ + public function updateCashBalance($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s/cash_balance', $parentId), $params, $opts); + } + + /** + * Update a specified source for a given customer. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public function updateSource($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s/sources/%s', $parentId, $id), $params, $opts); + } + + /** + * Verify a specified bank account for a given customer. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public function verifySource($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/customers/%s/sources/%s/verify', $parentId, $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/CustomerSessionService.php b/libraries/Stripe/lib/Service/CustomerSessionService.php new file mode 100644 index 00000000000..31fa1af1c44 --- /dev/null +++ b/libraries/Stripe/lib/Service/CustomerSessionService.php @@ -0,0 +1,29 @@ +request('post', '/v1/customer_sessions', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/DisputeService.php b/libraries/Stripe/lib/Service/DisputeService.php new file mode 100644 index 00000000000..f50cb8f40ec --- /dev/null +++ b/libraries/Stripe/lib/Service/DisputeService.php @@ -0,0 +1,87 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/disputes', $params, $opts); + } + + /** + * Closing the dispute for a charge indicates that you do not have any evidence to + * submit and are essentially dismissing the dispute, acknowledging it as lost. + * + * The status of the dispute will change from needs_response to + * lost. Closing a dispute is irreversible. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Dispute + */ + public function close($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/disputes/%s/close', $id), $params, $opts); + } + + /** + * Retrieves the dispute with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Dispute + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/disputes/%s', $id), $params, $opts); + } + + /** + * When you get a dispute, contacting your customer is always the best first step. + * If that doesn’t work, you can submit evidence to help us resolve the dispute in + * your favor. You can do this in your dashboard, but if you prefer, + * you can use the API to submit evidence programmatically. + * + * Depending on your dispute type, different evidence fields will give you a better + * chance of winning your dispute. To figure out which evidence fields to provide, + * see our guide to dispute types. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Dispute + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/disputes/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Entitlements/ActiveEntitlementService.php b/libraries/Stripe/lib/Service/Entitlements/ActiveEntitlementService.php new file mode 100644 index 00000000000..5537f13daaf --- /dev/null +++ b/libraries/Stripe/lib/Service/Entitlements/ActiveEntitlementService.php @@ -0,0 +1,43 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/entitlements/active_entitlements', $params, $opts); + } + + /** + * Retrieve an active entitlement. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Entitlements\ActiveEntitlement + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/entitlements/active_entitlements/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Entitlements/EntitlementsServiceFactory.php b/libraries/Stripe/lib/Service/Entitlements/EntitlementsServiceFactory.php new file mode 100644 index 00000000000..eff7c7a8602 --- /dev/null +++ b/libraries/Stripe/lib/Service/Entitlements/EntitlementsServiceFactory.php @@ -0,0 +1,27 @@ + + */ + private static $classMap = [ + 'activeEntitlements' => ActiveEntitlementService::class, + 'features' => FeatureService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Entitlements/FeatureService.php b/libraries/Stripe/lib/Service/Entitlements/FeatureService.php new file mode 100644 index 00000000000..12237adcf96 --- /dev/null +++ b/libraries/Stripe/lib/Service/Entitlements/FeatureService.php @@ -0,0 +1,74 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/entitlements/features', $params, $opts); + } + + /** + * Creates a feature. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Entitlements\Feature + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/entitlements/features', $params, $opts); + } + + /** + * Retrieves a feature. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Entitlements\Feature + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/entitlements/features/%s', $id), $params, $opts); + } + + /** + * Update a feature’s metadata or permanently deactivate it. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Entitlements\Feature + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/entitlements/features/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/EphemeralKeyService.php b/libraries/Stripe/lib/Service/EphemeralKeyService.php new file mode 100644 index 00000000000..a3558a95dc1 --- /dev/null +++ b/libraries/Stripe/lib/Service/EphemeralKeyService.php @@ -0,0 +1,47 @@ +request('delete', $this->buildPath('/v1/ephemeral_keys/%s', $id), $params, $opts); + } + + /** + * Creates a short-lived API key for a given resource. + * + * @param null|array $params + * @param null|array|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\EphemeralKey + */ + public function create($params = null, $opts = null) + { + if (!$opts || !isset($opts['stripe_version'])) { + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('stripe_version must be specified to create an ephemeral key'); + } + + return $this->request('post', '/v1/ephemeral_keys', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/EventService.php b/libraries/Stripe/lib/Service/EventService.php new file mode 100644 index 00000000000..e0169fca069 --- /dev/null +++ b/libraries/Stripe/lib/Service/EventService.php @@ -0,0 +1,48 @@ +event object + * api_version attribute (not according to your current EDD\Vendor\Stripe API + * version or Stripe-Version header). + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Event> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/events', $params, $opts); + } + + /** + * Retrieves the details of an event if it was created in the last 30 days. Supply + * the unique identifier of the event, which you might have received in a webhook. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Event + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/events/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/ExchangeRateService.php b/libraries/Stripe/lib/Service/ExchangeRateService.php new file mode 100644 index 00000000000..4f703bb123a --- /dev/null +++ b/libraries/Stripe/lib/Service/ExchangeRateService.php @@ -0,0 +1,45 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/exchange_rates', $params, $opts); + } + + /** + * Retrieves the exchange rates from the given currency to every supported + * currency. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ExchangeRate + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/exchange_rates/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/FileLinkService.php b/libraries/Stripe/lib/Service/FileLinkService.php new file mode 100644 index 00000000000..7f1ddb0560c --- /dev/null +++ b/libraries/Stripe/lib/Service/FileLinkService.php @@ -0,0 +1,74 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/file_links', $params, $opts); + } + + /** + * Creates a new file link object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FileLink + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/file_links', $params, $opts); + } + + /** + * Retrieves the file link with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FileLink + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/file_links/%s', $id), $params, $opts); + } + + /** + * Updates an existing file link object. Expired links can no longer be updated. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FileLink + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/file_links/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/FileService.php b/libraries/Stripe/lib/Service/FileService.php new file mode 100644 index 00000000000..4e4657a48fb --- /dev/null +++ b/libraries/Stripe/lib/Service/FileService.php @@ -0,0 +1,69 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/files', $params, $opts); + } + + /** + * Retrieves the details of an existing file object. After you supply a unique file + * ID, EDD\Vendor\Stripe returns the corresponding file object. Learn how to access file contents. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\File + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/files/%s', $id), $params, $opts); + } + + /** + * Create a file. + * + * @param null|array $params + * @param null|array|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @return \EDD\Vendor\Stripe\File + */ + public function create($params = null, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + if (!isset($opts->apiBase)) { + $opts->apiBase = $this->getClient()->getFilesBase(); + } + + // Manually flatten params, otherwise curl's multipart encoder will + // choke on nested null|arrays. + $flatParams = \array_column(\EDD\Vendor\Stripe\Util\Util::flattenParams($params), 1, 0); + + return $this->request('post', '/v1/files', $flatParams, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/FinancialConnections/AccountService.php b/libraries/Stripe/lib/Service/FinancialConnections/AccountService.php new file mode 100644 index 00000000000..83e33621438 --- /dev/null +++ b/libraries/Stripe/lib/Service/FinancialConnections/AccountService.php @@ -0,0 +1,127 @@ +Account objects. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\FinancialConnections\Account> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/financial_connections/accounts', $params, $opts); + } + + /** + * Lists all owners for a given Account. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\FinancialConnections\AccountOwner> + */ + public function allOwners($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/financial_connections/accounts/%s/owners', $id), $params, $opts); + } + + /** + * Disables your access to a Financial Connections Account. You will + * no longer be able to access data associated with the account (e.g. balances, + * transactions). + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account + */ + public function disconnect($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/financial_connections/accounts/%s/disconnect', $id), $params, $opts); + } + + /** + * Refreshes the data associated with a Financial Connections Account. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account + */ + public function refresh($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/financial_connections/accounts/%s/refresh', $id), $params, $opts); + } + + /** + * Retrieves the details of an Financial Connections Account. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/financial_connections/accounts/%s', $id), $params, $opts); + } + + /** + * Subscribes to periodic refreshes of data associated with a Financial Connections + * Account. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account + */ + public function subscribe($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/financial_connections/accounts/%s/subscribe', $id), $params, $opts); + } + + /** + * Unsubscribes from periodic refreshes of data associated with a Financial + * Connections Account. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Account + */ + public function unsubscribe($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/financial_connections/accounts/%s/unsubscribe', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/FinancialConnections/FinancialConnectionsServiceFactory.php b/libraries/Stripe/lib/Service/FinancialConnections/FinancialConnectionsServiceFactory.php new file mode 100644 index 00000000000..3d8f3d5baaf --- /dev/null +++ b/libraries/Stripe/lib/Service/FinancialConnections/FinancialConnectionsServiceFactory.php @@ -0,0 +1,29 @@ + + */ + private static $classMap = [ + 'accounts' => AccountService::class, + 'sessions' => SessionService::class, + 'transactions' => TransactionService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/FinancialConnections/SessionService.php b/libraries/Stripe/lib/Service/FinancialConnections/SessionService.php new file mode 100644 index 00000000000..064c6fa4bf5 --- /dev/null +++ b/libraries/Stripe/lib/Service/FinancialConnections/SessionService.php @@ -0,0 +1,45 @@ +Session. The session’s client_secret can be used to + * launch the flow using Stripe.js. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Session + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/financial_connections/sessions', $params, $opts); + } + + /** + * Retrieves the details of a Financial Connections Session. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Session + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/financial_connections/sessions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/FinancialConnections/TransactionService.php b/libraries/Stripe/lib/Service/FinancialConnections/TransactionService.php new file mode 100644 index 00000000000..3784bb7ef6a --- /dev/null +++ b/libraries/Stripe/lib/Service/FinancialConnections/TransactionService.php @@ -0,0 +1,43 @@ +Transaction objects. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\FinancialConnections\Transaction> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/financial_connections/transactions', $params, $opts); + } + + /** + * Retrieves the details of a Financial Connections Transaction. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\FinancialConnections\Transaction + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/financial_connections/transactions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Forwarding/ForwardingServiceFactory.php b/libraries/Stripe/lib/Service/Forwarding/ForwardingServiceFactory.php new file mode 100644 index 00000000000..3541375fb5e --- /dev/null +++ b/libraries/Stripe/lib/Service/Forwarding/ForwardingServiceFactory.php @@ -0,0 +1,25 @@ + + */ + private static $classMap = [ + 'requests' => RequestService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Forwarding/RequestService.php b/libraries/Stripe/lib/Service/Forwarding/RequestService.php new file mode 100644 index 00000000000..116d28b372d --- /dev/null +++ b/libraries/Stripe/lib/Service/Forwarding/RequestService.php @@ -0,0 +1,58 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/forwarding/requests', $params, $opts); + } + + /** + * Creates a ForwardingRequest object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Forwarding\Request + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/forwarding/requests', $params, $opts); + } + + /** + * Retrieves a ForwardingRequest object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Forwarding\Request + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/forwarding/requests/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Identity/IdentityServiceFactory.php b/libraries/Stripe/lib/Service/Identity/IdentityServiceFactory.php new file mode 100644 index 00000000000..b21cae0f4bd --- /dev/null +++ b/libraries/Stripe/lib/Service/Identity/IdentityServiceFactory.php @@ -0,0 +1,27 @@ + + */ + private static $classMap = [ + 'verificationReports' => VerificationReportService::class, + 'verificationSessions' => VerificationSessionService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Identity/VerificationReportService.php b/libraries/Stripe/lib/Service/Identity/VerificationReportService.php new file mode 100644 index 00000000000..a65de5d03e6 --- /dev/null +++ b/libraries/Stripe/lib/Service/Identity/VerificationReportService.php @@ -0,0 +1,43 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/identity/verification_reports', $params, $opts); + } + + /** + * Retrieves an existing VerificationReport. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationReport + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/identity/verification_reports/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Identity/VerificationSessionService.php b/libraries/Stripe/lib/Service/Identity/VerificationSessionService.php new file mode 100644 index 00000000000..503218d2973 --- /dev/null +++ b/libraries/Stripe/lib/Service/Identity/VerificationSessionService.php @@ -0,0 +1,150 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/identity/verification_sessions', $params, $opts); + } + + /** + * A VerificationSession object can be canceled when it is in + * requires_input status. + * + * Once canceled, future submission attempts are disabled. This cannot be undone. + * Learn more. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/identity/verification_sessions/%s/cancel', $id), $params, $opts); + } + + /** + * Creates a VerificationSession object. + * + * After the VerificationSession is created, display a verification modal using the + * session client_secret or send your users to the session’s + * url. + * + * If your API key is in test mode, verification checks won’t actually process, + * though everything else will occur as if in live mode. + * + * Related guide: Verify your + * users’ identity documents + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/identity/verification_sessions', $params, $opts); + } + + /** + * Redact a VerificationSession to remove all collected information from Stripe. + * This will redact the VerificationSession and all objects related to it, + * including VerificationReports, Events, request logs, etc. + * + * A VerificationSession object can be redacted when it is in + * requires_input or verified status. Redacting a + * VerificationSession in requires_action state will automatically + * cancel it. + * + * The redaction process may take up to four days. When the redaction process is in + * progress, the VerificationSession’s redaction.status field will be + * set to processing; when the process is finished, it will change to + * redacted and an identity.verification_session.redacted + * event will be emitted. + * + * Redaction is irreversible. Redacted objects are still accessible in the EDD\Vendor\Stripe + * API, but all the fields that contain personal data will be replaced by the + * string [redacted] or a similar placeholder. The + * metadata field will also be erased. Redacted objects cannot be + * updated or used for any purpose. + * + * Learn more. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession + */ + public function redact($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/identity/verification_sessions/%s/redact', $id), $params, $opts); + } + + /** + * Retrieves the details of a VerificationSession that was previously created. + * + * When the session status is requires_input, you can use this method + * to retrieve a valid client_secret or url to allow + * re-submission. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/identity/verification_sessions/%s', $id), $params, $opts); + } + + /** + * Updates a VerificationSession object. + * + * When the session status is requires_input, you can use this method + * to update the verification check and options. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Identity\VerificationSession + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/identity/verification_sessions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/InvoiceItemService.php b/libraries/Stripe/lib/Service/InvoiceItemService.php new file mode 100644 index 00000000000..178a269e2d7 --- /dev/null +++ b/libraries/Stripe/lib/Service/InvoiceItemService.php @@ -0,0 +1,97 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/invoiceitems', $params, $opts); + } + + /** + * Creates an item to be added to a draft invoice (up to 250 items per invoice). If + * no invoice is specified, the item will be on the next invoice created for the + * customer specified. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceItem + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/invoiceitems', $params, $opts); + } + + /** + * Deletes an invoice item, removing it from an invoice. Deleting invoice items is + * only possible when they’re not attached to invoices, or if it’s attached to a + * draft invoice. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceItem + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/invoiceitems/%s', $id), $params, $opts); + } + + /** + * Retrieves the invoice item with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceItem + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/invoiceitems/%s', $id), $params, $opts); + } + + /** + * Updates the amount or description of an invoice item on an upcoming invoice. + * Updating an invoice item is only possible before the invoice it’s attached to is + * closed. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceItem + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoiceitems/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/InvoiceRenderingTemplateService.php b/libraries/Stripe/lib/Service/InvoiceRenderingTemplateService.php new file mode 100644 index 00000000000..4fd9f2d623b --- /dev/null +++ b/libraries/Stripe/lib/Service/InvoiceRenderingTemplateService.php @@ -0,0 +1,82 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/invoice_rendering_templates', $params, $opts); + } + + /** + * Updates the status of an invoice rendering template to ‘archived’ so no new + * EDD\Vendor\Stripe objects (customers, invoices, etc.) can reference it. The template can + * also no longer be updated. However, if the template is already set on a EDD\Vendor\Stripe + * object, it will continue to be applied on invoices generated by it. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceRenderingTemplate + */ + public function archive($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoice_rendering_templates/%s/archive', $id), $params, $opts); + } + + /** + * Retrieves an invoice rendering template with the given ID. It by default returns + * the latest version of the template. Optionally, specify a version to see + * previous versions. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceRenderingTemplate + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/invoice_rendering_templates/%s', $id), $params, $opts); + } + + /** + * Unarchive an invoice rendering template so it can be used on new EDD\Vendor\Stripe objects + * again. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceRenderingTemplate + */ + public function unarchive($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoice_rendering_templates/%s/unarchive', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/InvoiceService.php b/libraries/Stripe/lib/Service/InvoiceService.php new file mode 100644 index 00000000000..6e4287e070a --- /dev/null +++ b/libraries/Stripe/lib/Service/InvoiceService.php @@ -0,0 +1,416 @@ +request('post', $this->buildPath('/v1/invoices/%s/add_lines', $id), $params, $opts); + } + + /** + * You can list all invoices, or list the invoices for a specific customer. The + * invoices are returned sorted by creation date, with the most recently created + * invoices appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Invoice> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/invoices', $params, $opts); + } + + /** + * When retrieving an invoice, you’ll get a lines property + * containing the total count of line items and the first handful of those items. + * There is also a URL where you can retrieve the full (paginated) list of line + * items. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\InvoiceLineItem> + */ + public function allLines($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/invoices/%s/lines', $parentId), $params, $opts); + } + + /** + * This endpoint creates a draft invoice for a given customer. The invoice remains + * a draft until you finalize the invoice, which + * allows you to pay or send + * the invoice to your customers. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/invoices', $params, $opts); + } + + /** + * At any time, you can preview the upcoming invoice for a customer. This will show + * you all the charges that are pending, including subscription renewal charges, + * invoice item charges, etc. It will also show you any discounts that are + * applicable to the invoice. + * + * Note that when you are viewing an upcoming invoice, you are simply viewing a + * preview – the invoice has not yet been created. As such, the upcoming invoice + * will not show up in invoice listing calls, and you cannot use the API to pay or + * edit the invoice. If you want to change the amount that your customer will be + * billed, you can add, remove, or update pending invoice items, or update the + * customer’s discount. + * + * You can preview the effects of updating a subscription, including a preview of + * what proration will take place. To ensure that the actual proration is + * calculated exactly the same as the previewed proration, you should pass the + * subscription_details.proration_date parameter when doing the actual + * subscription update. The recommended way to get only the prorations being + * previewed is to consider only proration line items where + * period[start] is equal to the + * subscription_details.proration_date value passed in the request. + * + * Note: Currency conversion calculations use the latest exchange rates. Exchange + * rates may vary between the time of the preview and the time of the actual + * invoice creation. Learn + * more + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function createPreview($params = null, $opts = null) + { + return $this->request('post', '/v1/invoices/create_preview', $params, $opts); + } + + /** + * Permanently deletes a one-off invoice draft. This cannot be undone. Attempts to + * delete invoices that are no longer in a draft state will fail; once an invoice + * has been finalized or if an invoice is for a subscription, it must be voided. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/invoices/%s', $id), $params, $opts); + } + + /** + * EDD\Vendor\Stripe automatically finalizes drafts before sending and attempting payment on + * invoices. However, if you’d like to finalize a draft invoice manually, you can + * do so using this method. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function finalizeInvoice($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s/finalize', $id), $params, $opts); + } + + /** + * Marking an invoice as uncollectible is useful for keeping track of bad debts + * that can be written off for accounting purposes. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function markUncollectible($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s/mark_uncollectible', $id), $params, $opts); + } + + /** + * EDD\Vendor\Stripe automatically creates and then attempts to collect payment on invoices + * for customers on subscriptions according to your subscriptions + * settings. However, if you’d like to attempt payment on an invoice out of the + * normal collection schedule or for some other reason, you can do so. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function pay($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s/pay', $id), $params, $opts); + } + + /** + * Removes multiple line items from an invoice. This is only possible when an + * invoice is still a draft. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function removeLines($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s/remove_lines', $id), $params, $opts); + } + + /** + * Retrieves the invoice with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/invoices/%s', $id), $params, $opts); + } + + /** + * Search for invoices you’ve previously created using Stripe’s Search Query Language. Don’t use + * search in read-after-write flows where strict consistency is necessary. Under + * normal operating conditions, data is searchable in less than a minute. + * Occasionally, propagation of new or updated data can be up to an hour behind + * during outages. Search functionality is not available to merchants in India. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Invoice> + */ + public function search($params = null, $opts = null) + { + return $this->requestSearchResult('get', '/v1/invoices/search', $params, $opts); + } + + /** + * EDD\Vendor\Stripe will automatically send invoices to customers according to your subscriptions + * settings. However, if you’d like to manually send an invoice to your + * customer out of the normal schedule, you can do so. When sending invoices that + * have already been paid, there will be no reference to the payment in the email. + * + * Requests made in test-mode result in no emails being sent, despite sending an + * invoice.sent event. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function sendInvoice($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s/send', $id), $params, $opts); + } + + /** + * At any time, you can preview the upcoming invoice for a customer. This will show + * you all the charges that are pending, including subscription renewal charges, + * invoice item charges, etc. It will also show you any discounts that are + * applicable to the invoice. + * + * Note that when you are viewing an upcoming invoice, you are simply viewing a + * preview – the invoice has not yet been created. As such, the upcoming invoice + * will not show up in invoice listing calls, and you cannot use the API to pay or + * edit the invoice. If you want to change the amount that your customer will be + * billed, you can add, remove, or update pending invoice items, or update the + * customer’s discount. + * + * You can preview the effects of updating a subscription, including a preview of + * what proration will take place. To ensure that the actual proration is + * calculated exactly the same as the previewed proration, you should pass the + * subscription_details.proration_date parameter when doing the actual + * subscription update. The recommended way to get only the prorations being + * previewed is to consider only proration line items where + * period[start] is equal to the + * subscription_details.proration_date value passed in the request. + * + * Note: Currency conversion calculations use the latest exchange rates. Exchange + * rates may vary between the time of the preview and the time of the actual + * invoice creation. Learn + * more + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function upcoming($params = null, $opts = null) + { + return $this->request('get', '/v1/invoices/upcoming', $params, $opts); + } + + /** + * When retrieving an upcoming invoice, you’ll get a lines + * property containing the total count of line items and the first handful of those + * items. There is also a URL where you can retrieve the full (paginated) list of + * line items. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\InvoiceLineItem> + */ + public function upcomingLines($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/invoices/upcoming/lines', $params, $opts); + } + + /** + * Draft invoices are fully editable. Once an invoice is finalized, monetary values, + * as well as collection_method, become uneditable. + * + * If you would like to stop the EDD\Vendor\Stripe Billing engine from automatically + * finalizing, reattempting payments on, sending reminders for, or automatically reconciling + * invoices, pass auto_advance=false. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s', $id), $params, $opts); + } + + /** + * Updates an invoice’s line item. Some fields, such as tax_amounts, + * only live on the invoice line item, so they can only be updated through this + * endpoint. Other fields, such as amount, live on both the invoice + * item and the invoice line item, so updates on this endpoint will propagate to + * the invoice item as well. Updating an invoice’s line item is only possible + * before the invoice is finalized. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\InvoiceLineItem + */ + public function updateLine($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s/lines/%s', $parentId, $id), $params, $opts); + } + + /** + * Updates multiple line items on an invoice. This is only possible when an invoice + * is still a draft. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function updateLines($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s/update_lines', $id), $params, $opts); + } + + /** + * Mark a finalized invoice as void. This cannot be undone. Voiding an invoice is + * similar to deletion, however it only applies to + * finalized invoices and maintains a papertrail where the invoice can still be + * found. + * + * Consult with local regulations to determine whether and how an invoice might be + * amended, canceled, or voided in the jurisdiction you’re doing business in. You + * might need to issue another invoice or credit note instead. EDD\Vendor\Stripe recommends that you + * consult with your legal counsel for advice specific to your business. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Invoice + */ + public function voidInvoice($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/invoices/%s/void', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/AuthorizationService.php b/libraries/Stripe/lib/Service/Issuing/AuthorizationService.php new file mode 100644 index 00000000000..fcd563716ff --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/AuthorizationService.php @@ -0,0 +1,109 @@ +Authorization objects. The objects are + * sorted in descending order by creation date, with the most recently created + * object appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Authorization> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/issuing/authorizations', $params, $opts); + } + + /** + * [Deprecated] Approves a pending Issuing Authorization object. This + * request should be made within the timeout window of the real-time + * authorization flow. This method is deprecated. Instead, respond + * directly to the webhook request to approve an authorization. + * + * @deprecated this method is deprecated, please refer to the description for details + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function approve($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/authorizations/%s/approve', $id), $params, $opts); + } + + /** + * [Deprecated] Declines a pending Issuing Authorization object. This + * request should be made within the timeout window of the real time + * authorization flow. This method is deprecated. Instead, respond + * directly to the webhook request to decline an authorization. + * + * @deprecated this method is deprecated, please refer to the description for details + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function decline($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/authorizations/%s/decline', $id), $params, $opts); + } + + /** + * Retrieves an Issuing Authorization object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/issuing/authorizations/%s', $id), $params, $opts); + } + + /** + * Updates the specified Issuing Authorization object by setting the + * values of the parameters passed. Any parameters not provided will be left + * unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/authorizations/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/CardService.php b/libraries/Stripe/lib/Service/Issuing/CardService.php new file mode 100644 index 00000000000..7f35f1c8eb6 --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/CardService.php @@ -0,0 +1,77 @@ +Card objects. The objects are sorted in + * descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Card> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/issuing/cards', $params, $opts); + } + + /** + * Creates an Issuing Card object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/issuing/cards', $params, $opts); + } + + /** + * Retrieves an Issuing Card object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/issuing/cards/%s', $id), $params, $opts); + } + + /** + * Updates the specified Issuing Card object by setting the values of + * the parameters passed. Any parameters not provided will be left unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/cards/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/CardholderService.php b/libraries/Stripe/lib/Service/Issuing/CardholderService.php new file mode 100644 index 00000000000..af4df96e846 --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/CardholderService.php @@ -0,0 +1,78 @@ +Cardholder objects. The objects are + * sorted in descending order by creation date, with the most recently created + * object appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Cardholder> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/issuing/cardholders', $params, $opts); + } + + /** + * Creates a new Issuing Cardholder object that can be issued cards. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Cardholder + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/issuing/cardholders', $params, $opts); + } + + /** + * Retrieves an Issuing Cardholder object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Cardholder + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/issuing/cardholders/%s', $id), $params, $opts); + } + + /** + * Updates the specified Issuing Cardholder object by setting the + * values of the parameters passed. Any parameters not provided will be left + * unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Cardholder + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/cardholders/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/DisputeService.php b/libraries/Stripe/lib/Service/Issuing/DisputeService.php new file mode 100644 index 00000000000..7d05e136b9f --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/DisputeService.php @@ -0,0 +1,103 @@ +Dispute objects. The objects are sorted + * in descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Dispute> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/issuing/disputes', $params, $opts); + } + + /** + * Creates an Issuing Dispute object. Individual pieces of evidence + * within the evidence object are optional at this point. EDD\Vendor\Stripe only + * validates that required evidence is present during submission. Refer to Dispute + * reasons and evidence for more details about evidence requirements. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Dispute + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/issuing/disputes', $params, $opts); + } + + /** + * Retrieves an Issuing Dispute object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Dispute + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/issuing/disputes/%s', $id), $params, $opts); + } + + /** + * Submits an Issuing Dispute to the card network. EDD\Vendor\Stripe validates + * that all evidence fields required for the dispute’s reason are present. For more + * details, see Dispute + * reasons and evidence. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Dispute + */ + public function submit($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/disputes/%s/submit', $id), $params, $opts); + } + + /** + * Updates the specified Issuing Dispute object by setting the values + * of the parameters passed. Any parameters not provided will be left unchanged. + * Properties on the evidence object can be unset by passing in an + * empty string. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Dispute + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/disputes/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/IssuingServiceFactory.php b/libraries/Stripe/lib/Service/Issuing/IssuingServiceFactory.php new file mode 100644 index 00000000000..291477264a3 --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/IssuingServiceFactory.php @@ -0,0 +1,39 @@ + + */ + private static $classMap = [ + 'authorizations' => AuthorizationService::class, + 'cardholders' => CardholderService::class, + 'cards' => CardService::class, + 'disputes' => DisputeService::class, + 'personalizationDesigns' => PersonalizationDesignService::class, + 'physicalBundles' => PhysicalBundleService::class, + 'tokens' => TokenService::class, + 'transactions' => TransactionService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/PersonalizationDesignService.php b/libraries/Stripe/lib/Service/Issuing/PersonalizationDesignService.php new file mode 100644 index 00000000000..adaf041d631 --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/PersonalizationDesignService.php @@ -0,0 +1,76 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/issuing/personalization_designs', $params, $opts); + } + + /** + * Creates a personalization design object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/issuing/personalization_designs', $params, $opts); + } + + /** + * Retrieves a personalization design object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/issuing/personalization_designs/%s', $id), $params, $opts); + } + + /** + * Updates a card personalization object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/personalization_designs/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/PhysicalBundleService.php b/libraries/Stripe/lib/Service/Issuing/PhysicalBundleService.php new file mode 100644 index 00000000000..6f1e1713667 --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/PhysicalBundleService.php @@ -0,0 +1,44 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/issuing/physical_bundles', $params, $opts); + } + + /** + * Retrieves a physical bundle object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PhysicalBundle + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/issuing/physical_bundles/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/TokenService.php b/libraries/Stripe/lib/Service/Issuing/TokenService.php new file mode 100644 index 00000000000..0e8e917bd87 --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/TokenService.php @@ -0,0 +1,60 @@ +Token objects for a given card. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Token> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/issuing/tokens', $params, $opts); + } + + /** + * Retrieves an Issuing Token object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Token + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/issuing/tokens/%s', $id), $params, $opts); + } + + /** + * Attempts to update the specified Issuing Token object to the status + * specified. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Token + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/tokens/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Issuing/TransactionService.php b/libraries/Stripe/lib/Service/Issuing/TransactionService.php new file mode 100644 index 00000000000..049c6fd6cf9 --- /dev/null +++ b/libraries/Stripe/lib/Service/Issuing/TransactionService.php @@ -0,0 +1,63 @@ +Transaction objects. The objects are + * sorted in descending order by creation date, with the most recently created + * object appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Issuing\Transaction> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/issuing/transactions', $params, $opts); + } + + /** + * Retrieves an Issuing Transaction object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Transaction + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/issuing/transactions/%s', $id), $params, $opts); + } + + /** + * Updates the specified Issuing Transaction object by setting the + * values of the parameters passed. Any parameters not provided will be left + * unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Transaction + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/issuing/transactions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/MandateService.php b/libraries/Stripe/lib/Service/MandateService.php new file mode 100644 index 00000000000..39783192529 --- /dev/null +++ b/libraries/Stripe/lib/Service/MandateService.php @@ -0,0 +1,28 @@ +request('get', $this->buildPath('/v1/mandates/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/OAuthService.php b/libraries/Stripe/lib/Service/OAuthService.php new file mode 100644 index 00000000000..238541f750b --- /dev/null +++ b/libraries/Stripe/lib/Service/OAuthService.php @@ -0,0 +1,150 @@ +_parseOpts($opts); + $opts->apiBase = $this->_getBase($opts); + + return $this->request($method, $path, $params, $opts); + } + + /** + * Generates a URL to Stripe's OAuth form. + * + * @param null|array $params + * @param null|array $opts + * + * @return string the URL to Stripe's OAuth form + */ + public function authorizeUrl($params = null, $opts = null) + { + $params = $params ?: []; + + $opts = $this->_parseOpts($opts); + $base = $this->_getBase($opts); + + $params['client_id'] = $this->_getClientId($params); + if (!\array_key_exists('response_type', $params)) { + $params['response_type'] = 'code'; + } + $query = \EDD\Vendor\Stripe\Util\Util::encodeParameters($params); + + return $base . '/oauth/authorize?' . $query; + } + + /** + * Use an authoriztion code to connect an account to your platform and + * fetch the user's credentials. + * + * @param null|array $params + * @param null|array $opts + * + * @throws \EDD\Vendor\Stripe\Exception\OAuth\OAuthErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\StripeObject object containing the response from the API + */ + public function token($params = null, $opts = null) + { + $params = $params ?: []; + $params['client_secret'] = $this->_getClientSecret($params); + + return $this->requestConnect('post', '/oauth/token', $params, $opts); + } + + /** + * Disconnects an account from your platform. + * + * @param null|array $params + * @param null|array $opts + * + * @throws \EDD\Vendor\Stripe\Exception\OAuth\OAuthErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\StripeObject object containing the response from the API + */ + public function deauthorize($params = null, $opts = null) + { + $params = $params ?: []; + $params['client_id'] = $this->_getClientId($params); + + return $this->requestConnect('post', '/oauth/deauthorize', $params, $opts); + } + + private function _getClientId($params = null) + { + $clientId = ($params && \array_key_exists('client_id', $params)) ? $params['client_id'] : null; + + if (null === $clientId) { + $clientId = $this->client->getClientId(); + } + if (null === $clientId) { + $msg = 'No client_id provided. (HINT: set your client_id using ' + . '`new \EDD\Vendor\Stripe\StripeClient([clientId => + ])`)". You can find your client_ids ' + . 'in your EDD\Vendor\Stripe dashboard at ' + . 'https://dashboard.stripe.com/account/applications/settings, ' + . 'after registering your account as a platform. See ' + . 'https://stripe.com/docs/connect/standard-accounts for details, ' + . 'or email support@stripe.com if you have any questions.'; + + throw new \EDD\Vendor\Stripe\Exception\AuthenticationException($msg); + } + + return $clientId; + } + + private function _getClientSecret($params = null) + { + if (\array_key_exists('client_secret', $params)) { + return $params['client_secret']; + } + + return $this->client->getApiKey(); + } + + /** + * @param array|\EDD\Vendor\Stripe\Util\RequestOptions $opts the special modifiers of the request + * + * @throws \EDD\Vendor\Stripe\Exception\InvalidArgumentException + * + * @return \EDD\Vendor\Stripe\Util\RequestOptions + */ + private function _parseOpts($opts) + { + if (\is_array($opts)) { + if (\array_key_exists('connect_base', $opts)) { + // Throw an exception for the convenience of anybody migrating to + // \EDD\Vendor\Stripe\Service\OAuthService from \EDD\Vendor\Stripe\OAuth, where `connect_base` + // was the name of the parameter that behaves as `api_base` does here. + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException('Use `api_base`, not `connect_base`'); + } + } + + return \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + } + + /** + * @param \EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @return string + */ + private function _getBase($opts) + { + return isset($opts->apiBase) ? + $opts->apiBase : + $this->client->getConnectBase(); + } +} diff --git a/libraries/Stripe/lib/Service/PaymentIntentService.php b/libraries/Stripe/lib/Service/PaymentIntentService.php new file mode 100644 index 00000000000..ca6dae0160c --- /dev/null +++ b/libraries/Stripe/lib/Service/PaymentIntentService.php @@ -0,0 +1,283 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/payment_intents', $params, $opts); + } + + /** + * Manually reconcile the remaining amount for a customer_balance + * PaymentIntent. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function applyCustomerBalance($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_intents/%s/apply_customer_balance', $id), $params, $opts); + } + + /** + * You can cancel a PaymentIntent object when it’s in one of these statuses: + * requires_payment_method, requires_capture, + * requires_confirmation, requires_action or, in rare cases, processing. + * + * After it’s canceled, no additional charges are made by the PaymentIntent and any + * operations on the PaymentIntent fail with an error. For PaymentIntents with a + * status of requires_capture, the remaining + * amount_capturable is automatically refunded. + * + * You can’t cancel the PaymentIntent for a Checkout Session. Expire the Checkout Session + * instead. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_intents/%s/cancel', $id), $params, $opts); + } + + /** + * Capture the funds of an existing uncaptured PaymentIntent when its status is + * requires_capture. + * + * Uncaptured PaymentIntents are cancelled a set number of days (7 by default) + * after their creation. + * + * Learn more about separate authorization + * and capture. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function capture($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_intents/%s/capture', $id), $params, $opts); + } + + /** + * Confirm that your customer intends to pay with current or provided payment + * method. Upon confirmation, the PaymentIntent will attempt to initiate a payment. + * If the selected payment method requires additional authentication steps, the + * PaymentIntent will transition to the requires_action status and + * suggest additional actions via next_action. If payment fails, the + * PaymentIntent transitions to the requires_payment_method status or + * the canceled status if the confirmation limit is reached. If + * payment succeeds, the PaymentIntent will transition to the + * succeeded status (or requires_capture, if + * capture_method is set to manual). If the + * confirmation_method is automatic, payment may be + * attempted using our client SDKs and + * the PaymentIntent’s client_secret. After + * next_actions are handled by the client, no additional confirmation + * is required to complete the payment. If the confirmation_method is + * manual, all payment attempts must be initiated using a secret key. + * If any actions are required for the payment, the PaymentIntent will return to + * the requires_confirmation state after those actions are completed. + * Your server needs to then explicitly re-confirm the PaymentIntent to initiate + * the next payment attempt. There is a variable upper limit on how many times a + * PaymentIntent can be confirmed. After this limit is reached, any further calls + * to this endpoint will transition the PaymentIntent to the canceled + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function confirm($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_intents/%s/confirm', $id), $params, $opts); + } + + /** + * Creates a PaymentIntent object. + * + * After the PaymentIntent is created, attach a payment method and confirm to continue the payment. + * Learn more about the available payment + * flows with the Payment Intents API. + * + * When you use confirm=true during creation, it’s equivalent to + * creating and confirming the PaymentIntent in the same call. You can use any + * parameters available in the confirm + * API when you supply confirm=true. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/payment_intents', $params, $opts); + } + + /** + * Perform an incremental authorization on an eligible PaymentIntent. To be eligible, the + * PaymentIntent’s status must be requires_capture and incremental_authorization_supported + * must be true. + * + * Incremental authorizations attempt to increase the authorized amount on your + * customer’s card to the new, higher amount provided. Similar to the + * initial authorization, incremental authorizations can be declined. A single + * PaymentIntent can call this endpoint multiple times to further increase the + * authorized amount. + * + * If the incremental authorization succeeds, the PaymentIntent object returns with + * the updated amount. + * If the incremental authorization fails, a card_declined error returns, and no + * other fields on the PaymentIntent or Charge update. The PaymentIntent object + * remains capturable for the previously authorized amount. + * + * Each PaymentIntent can have a maximum of 10 incremental authorization attempts, + * including declines. After it’s captured, a PaymentIntent can no longer be + * incremented. + * + * Learn more about incremental + * authorizations. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function incrementAuthorization($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_intents/%s/increment_authorization', $id), $params, $opts); + } + + /** + * Retrieves the details of a PaymentIntent that has previously been created. + * + * You can retrieve a PaymentIntent client-side using a publishable key when the + * client_secret is in the query string. + * + * If you retrieve a PaymentIntent with a publishable key, it only returns a subset + * of properties. Refer to the payment intent + * object reference for more details. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/payment_intents/%s', $id), $params, $opts); + } + + /** + * Search for PaymentIntents you’ve previously created using Stripe’s Search Query Language. Don’t use + * search in read-after-write flows where strict consistency is necessary. Under + * normal operating conditions, data is searchable in less than a minute. + * Occasionally, propagation of new or updated data can be up to an hour behind + * during outages. Search functionality is not available to merchants in India. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\PaymentIntent> + */ + public function search($params = null, $opts = null) + { + return $this->requestSearchResult('get', '/v1/payment_intents/search', $params, $opts); + } + + /** + * Updates properties on a PaymentIntent object without confirming. + * + * Depending on which properties you update, you might need to confirm the + * PaymentIntent again. For example, updating the payment_method + * always requires you to confirm the PaymentIntent again. If you prefer to update + * and confirm at the same time, we recommend updating properties through the confirm API instead. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_intents/%s', $id), $params, $opts); + } + + /** + * Verifies microdeposits on a PaymentIntent object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentIntent + */ + public function verifyMicrodeposits($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_intents/%s/verify_microdeposits', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/PaymentLinkService.php b/libraries/Stripe/lib/Service/PaymentLinkService.php new file mode 100644 index 00000000000..e6eae079af6 --- /dev/null +++ b/libraries/Stripe/lib/Service/PaymentLinkService.php @@ -0,0 +1,93 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/payment_links', $params, $opts); + } + + /** + * When retrieving a payment link, there is an includable + * line_items property containing the first handful of those + * items. There is also a URL where you can retrieve the full (paginated) list of + * line items. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> + */ + public function allLineItems($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/payment_links/%s/line_items', $id), $params, $opts); + } + + /** + * Creates a payment link. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentLink + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/payment_links', $params, $opts); + } + + /** + * Retrieve a payment link. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentLink + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/payment_links/%s', $id), $params, $opts); + } + + /** + * Updates a payment link. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentLink + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_links/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/PaymentMethodConfigurationService.php b/libraries/Stripe/lib/Service/PaymentMethodConfigurationService.php new file mode 100644 index 00000000000..c8bc8f66d94 --- /dev/null +++ b/libraries/Stripe/lib/Service/PaymentMethodConfigurationService.php @@ -0,0 +1,74 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/payment_method_configurations', $params, $opts); + } + + /** + * Creates a payment method configuration. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodConfiguration + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/payment_method_configurations', $params, $opts); + } + + /** + * Retrieve payment method configuration. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodConfiguration + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/payment_method_configurations/%s', $id), $params, $opts); + } + + /** + * Update payment method configuration. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodConfiguration + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_method_configurations/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/PaymentMethodDomainService.php b/libraries/Stripe/lib/Service/PaymentMethodDomainService.php new file mode 100644 index 00000000000..03a5014e87e --- /dev/null +++ b/libraries/Stripe/lib/Service/PaymentMethodDomainService.php @@ -0,0 +1,101 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/payment_method_domains', $params, $opts); + } + + /** + * Creates a payment method domain. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodDomain + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/payment_method_domains', $params, $opts); + } + + /** + * Retrieves the details of an existing payment method domain. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodDomain + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/payment_method_domains/%s', $id), $params, $opts); + } + + /** + * Updates an existing payment method domain. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodDomain + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_method_domains/%s', $id), $params, $opts); + } + + /** + * Some payment methods such as Apple Pay require additional steps to verify a + * domain. If the requirements weren’t satisfied when the domain was created, the + * payment method will be inactive on the domain. The payment method doesn’t appear + * in Elements for this domain until it is active. + * + * To activate a payment method on an existing payment method domain, complete the + * required validation steps specific to the payment method, and then validate the + * payment method domain with this endpoint. + * + * Related guides: Payment method + * domains. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethodDomain + */ + public function validate($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_method_domains/%s/validate', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/PaymentMethodService.php b/libraries/Stripe/lib/Service/PaymentMethodService.php new file mode 100644 index 00000000000..80451a2920d --- /dev/null +++ b/libraries/Stripe/lib/Service/PaymentMethodService.php @@ -0,0 +1,139 @@ +List a Customer’s + * PaymentMethods API instead. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\PaymentMethod> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/payment_methods', $params, $opts); + } + + /** + * Attaches a PaymentMethod object to a Customer. + * + * To attach a new PaymentMethod to a customer for future payments, we recommend + * you use a SetupIntent or a PaymentIntent + * with setup_future_usage. + * These approaches will perform any necessary steps to set up the PaymentMethod + * for future payments. Using the /v1/payment_methods/:id/attach + * endpoint without first using a SetupIntent or PaymentIntent with + * setup_future_usage does not optimize the PaymentMethod for future + * use, which makes later declines and payment friction more likely. See Optimizing cards for future + * payments for more information about setting up future payments. + * + * To use this PaymentMethod as the default for invoice or subscription payments, + * set invoice_settings.default_payment_method, + * on the Customer to the PaymentMethod’s ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod + */ + public function attach($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_methods/%s/attach', $id), $params, $opts); + } + + /** + * Creates a PaymentMethod object. Read the Stripe.js + * reference to learn how to create PaymentMethods via Stripe.js. + * + * Instead of creating a PaymentMethod directly, we recommend using the PaymentIntents API to accept a + * payment immediately or the SetupIntent API to collect payment + * method details ahead of a future payment. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/payment_methods', $params, $opts); + } + + /** + * Detaches a PaymentMethod object from a Customer. After a PaymentMethod is + * detached, it can no longer be used for a payment or re-attached to a Customer. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod + */ + public function detach($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_methods/%s/detach', $id), $params, $opts); + } + + /** + * Retrieves a PaymentMethod object attached to the StripeAccount. To retrieve a + * payment method attached to a Customer, you should use Retrieve a Customer’s + * PaymentMethods. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/payment_methods/%s', $id), $params, $opts); + } + + /** + * Updates a PaymentMethod object. A PaymentMethod must be attached a customer to + * be updated. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PaymentMethod + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payment_methods/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/PayoutService.php b/libraries/Stripe/lib/Service/PayoutService.php new file mode 100644 index 00000000000..282744b9d39 --- /dev/null +++ b/libraries/Stripe/lib/Service/PayoutService.php @@ -0,0 +1,131 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/payouts', $params, $opts); + } + + /** + * You can cancel a previously created payout if its status is + * pending. EDD\Vendor\Stripe refunds the funds to your available balance. You + * can’t cancel automatic EDD\Vendor\Stripe payouts. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payouts/%s/cancel', $id), $params, $opts); + } + + /** + * To send funds to your own bank account, create a new payout object. Your EDD\Vendor\Stripe balance must cover the payout amount. If it doesn’t, + * you receive an “Insufficient Funds” error. + * + * If your API key is in test mode, money won’t actually be sent, though every + * other action occurs as if you’re in live mode. + * + * If you create a manual payout on a EDD\Vendor\Stripe account that uses multiple payment + * source types, you need to specify the source type balance that the payout draws + * from. The balance object details available and + * pending amounts by source type. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/payouts', $params, $opts); + } + + /** + * Retrieves the details of an existing payout. Supply the unique payout ID from + * either a payout creation request or the payout list. EDD\Vendor\Stripe returns the + * corresponding payout information. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/payouts/%s', $id), $params, $opts); + } + + /** + * Reverses a payout by debiting the destination bank account. At this time, you + * can only reverse payouts for connected accounts to US bank accounts. If the + * payout is manual and in the pending status, use + * /v1/payouts/:id/cancel instead. + * + * By requesting a reversal through /v1/payouts/:id/reverse, you + * confirm that the authorized signatory of the selected bank account authorizes + * the debit on the bank account and that no other authorization is required. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout + */ + public function reverse($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payouts/%s/reverse', $id), $params, $opts); + } + + /** + * Updates the specified payout by setting the values of the parameters you pass. + * We don’t change parameters that you don’t provide. This request only accepts the + * metadata as arguments. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Payout + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/payouts/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/PlanService.php b/libraries/Stripe/lib/Service/PlanService.php new file mode 100644 index 00000000000..8b63cbfb1e2 --- /dev/null +++ b/libraries/Stripe/lib/Service/PlanService.php @@ -0,0 +1,95 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/plans', $params, $opts); + } + + /** + * You can now model subscriptions more flexibly using the Prices + * API. It replaces the Plans API and is backwards compatible to simplify your + * migration. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Plan + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/plans', $params, $opts); + } + + /** + * Deleting plans means new subscribers can’t be added. Existing subscribers aren’t + * affected. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Plan + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/plans/%s', $id), $params, $opts); + } + + /** + * Retrieves the plan with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Plan + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/plans/%s', $id), $params, $opts); + } + + /** + * Updates the specified plan by setting the values of the parameters passed. Any + * parameters not provided are left unchanged. By design, you cannot change a + * plan’s ID, amount, currency, or billing cycle. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Plan + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/plans/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/PriceService.php b/libraries/Stripe/lib/Service/PriceService.php new file mode 100644 index 00000000000..c5b6aa23d89 --- /dev/null +++ b/libraries/Stripe/lib/Service/PriceService.php @@ -0,0 +1,98 @@ +inline prices. + * For the list of inactive prices, set active to false. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Price> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/prices', $params, $opts); + } + + /** + * Creates a new price for an existing product. The price can be recurring or + * one-time. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Price + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/prices', $params, $opts); + } + + /** + * Retrieves the price with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Price + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/prices/%s', $id), $params, $opts); + } + + /** + * Search for prices you’ve previously created using Stripe’s Search Query Language. Don’t use + * search in read-after-write flows where strict consistency is necessary. Under + * normal operating conditions, data is searchable in less than a minute. + * Occasionally, propagation of new or updated data can be up to an hour behind + * during outages. Search functionality is not available to merchants in India. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Price> + */ + public function search($params = null, $opts = null) + { + return $this->requestSearchResult('get', '/v1/prices/search', $params, $opts); + } + + /** + * Updates the specified price by setting the values of the parameters passed. Any + * parameters not provided are left unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Price + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/prices/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/ProductService.php b/libraries/Stripe/lib/Service/ProductService.php new file mode 100644 index 00000000000..319b59a8d09 --- /dev/null +++ b/libraries/Stripe/lib/Service/ProductService.php @@ -0,0 +1,182 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/products', $params, $opts); + } + + /** + * Retrieve a list of features for a product. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ProductFeature> + */ + public function allFeatures($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/products/%s/features', $parentId), $params, $opts); + } + + /** + * Creates a new product object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Product + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/products', $params, $opts); + } + + /** + * Creates a product_feature, which represents a feature attachment to a product. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ProductFeature + */ + public function createFeature($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/products/%s/features', $parentId), $params, $opts); + } + + /** + * Delete a product. Deleting a product is only possible if it has no prices + * associated with it. Additionally, deleting a product with type=good + * is only possible if it has no SKUs associated with it. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Product + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/products/%s', $id), $params, $opts); + } + + /** + * Deletes the feature attachment to a product. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ProductFeature + */ + public function deleteFeature($parentId, $id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/products/%s/features/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieves the details of an existing product. Supply the unique product ID from + * either a product creation request or the product list, and EDD\Vendor\Stripe will return + * the corresponding product information. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Product + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/products/%s', $id), $params, $opts); + } + + /** + * Retrieves a product_feature, which represents a feature attachment to a product. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ProductFeature + */ + public function retrieveFeature($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/products/%s/features/%s', $parentId, $id), $params, $opts); + } + + /** + * Search for products you’ve previously created using Stripe’s Search Query Language. Don’t use + * search in read-after-write flows where strict consistency is necessary. Under + * normal operating conditions, data is searchable in less than a minute. + * Occasionally, propagation of new or updated data can be up to an hour behind + * during outages. Search functionality is not available to merchants in India. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Product> + */ + public function search($params = null, $opts = null) + { + return $this->requestSearchResult('get', '/v1/products/search', $params, $opts); + } + + /** + * Updates the specific product by setting the values of the parameters passed. Any + * parameters not provided will be left unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Product + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/products/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/PromotionCodeService.php b/libraries/Stripe/lib/Service/PromotionCodeService.php new file mode 100644 index 00000000000..c52c69b086c --- /dev/null +++ b/libraries/Stripe/lib/Service/PromotionCodeService.php @@ -0,0 +1,79 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/promotion_codes', $params, $opts); + } + + /** + * A promotion code points to a coupon. You can optionally restrict the code to a + * specific customer, redemption limit, and expiration date. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PromotionCode + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/promotion_codes', $params, $opts); + } + + /** + * Retrieves the promotion code with the given ID. In order to retrieve a promotion + * code by the customer-facing code use list with the desired + * code. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PromotionCode + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/promotion_codes/%s', $id), $params, $opts); + } + + /** + * Updates the specified promotion code by setting the values of the parameters + * passed. Most fields are, by design, not editable. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\PromotionCode + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/promotion_codes/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/QuoteService.php b/libraries/Stripe/lib/Service/QuoteService.php new file mode 100644 index 00000000000..c6ce41ae3a2 --- /dev/null +++ b/libraries/Stripe/lib/Service/QuoteService.php @@ -0,0 +1,185 @@ +request('post', $this->buildPath('/v1/quotes/%s/accept', $id), $params, $opts); + } + + /** + * Returns a list of your quotes. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Quote> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/quotes', $params, $opts); + } + + /** + * When retrieving a quote, there is an includable computed.upfront.line_items + * property containing the first handful of those items. There is also a URL where + * you can retrieve the full (paginated) list of upfront line items. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> + */ + public function allComputedUpfrontLineItems($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/quotes/%s/computed_upfront_line_items', $id), $params, $opts); + } + + /** + * When retrieving a quote, there is an includable line_items + * property containing the first handful of those items. There is also a URL where + * you can retrieve the full (paginated) list of line items. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\LineItem> + */ + public function allLineItems($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/quotes/%s/line_items', $id), $params, $opts); + } + + /** + * Cancels the quote. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/quotes/%s/cancel', $id), $params, $opts); + } + + /** + * A quote models prices and services for a customer. Default options for + * header, description, footer, and + * expires_at can be set in the dashboard via the quote template. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/quotes', $params, $opts); + } + + /** + * Finalizes the quote. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote + */ + public function finalizeQuote($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/quotes/%s/finalize', $id), $params, $opts); + } + + /** + * Download the PDF for a finalized quote. Explanation for special handling can be + * found here. + * + * @param string $id + * @param callable $readBodyChunkCallable + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return mixed + */ + public function pdf($id, $readBodyChunkCallable, $params = null, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + if (!isset($opts->apiBase)) { + $opts->apiBase = $this->getClient()->getFilesBase(); + } + + return $this->requestStream('get', $this->buildPath('/v1/quotes/%s/pdf', $id), $readBodyChunkCallable, $params, $opts); + } + + /** + * Retrieves the quote with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/quotes/%s', $id), $params, $opts); + } + + /** + * A quote models prices and services for a customer. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Quote + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/quotes/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Radar/EarlyFraudWarningService.php b/libraries/Stripe/lib/Service/Radar/EarlyFraudWarningService.php new file mode 100644 index 00000000000..b825d44ba64 --- /dev/null +++ b/libraries/Stripe/lib/Service/Radar/EarlyFraudWarningService.php @@ -0,0 +1,47 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/radar/early_fraud_warnings', $params, $opts); + } + + /** + * Retrieves the details of an early fraud warning that has previously been + * created. + * + * Please refer to the early fraud + * warning object reference for more details. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\EarlyFraudWarning + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/radar/early_fraud_warnings/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Radar/RadarServiceFactory.php b/libraries/Stripe/lib/Service/Radar/RadarServiceFactory.php new file mode 100644 index 00000000000..9491241f2c7 --- /dev/null +++ b/libraries/Stripe/lib/Service/Radar/RadarServiceFactory.php @@ -0,0 +1,29 @@ + + */ + private static $classMap = [ + 'earlyFraudWarnings' => EarlyFraudWarningService::class, + 'valueListItems' => ValueListItemService::class, + 'valueLists' => ValueListService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Radar/ValueListItemService.php b/libraries/Stripe/lib/Service/Radar/ValueListItemService.php new file mode 100644 index 00000000000..2abfea7b13b --- /dev/null +++ b/libraries/Stripe/lib/Service/Radar/ValueListItemService.php @@ -0,0 +1,78 @@ +ValueListItem objects. The objects are sorted in + * descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Radar\ValueListItem> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/radar/value_list_items', $params, $opts); + } + + /** + * Creates a new ValueListItem object, which is added to the specified + * parent value list. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueListItem + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/radar/value_list_items', $params, $opts); + } + + /** + * Deletes a ValueListItem object, removing it from its parent value + * list. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueListItem + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/radar/value_list_items/%s', $id), $params, $opts); + } + + /** + * Retrieves a ValueListItem object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueListItem + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/radar/value_list_items/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Radar/ValueListService.php b/libraries/Stripe/lib/Service/Radar/ValueListService.php new file mode 100644 index 00000000000..5eea433daa8 --- /dev/null +++ b/libraries/Stripe/lib/Service/Radar/ValueListService.php @@ -0,0 +1,97 @@ +ValueList objects. The objects are sorted in + * descending order by creation date, with the most recently created object + * appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Radar\ValueList> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/radar/value_lists', $params, $opts); + } + + /** + * Creates a new ValueList object, which can then be referenced in + * rules. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueList + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/radar/value_lists', $params, $opts); + } + + /** + * Deletes a ValueList object, also deleting any items contained + * within the value list. To be deleted, a value list must not be referenced in any + * rules. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueList + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/radar/value_lists/%s', $id), $params, $opts); + } + + /** + * Retrieves a ValueList object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueList + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/radar/value_lists/%s', $id), $params, $opts); + } + + /** + * Updates a ValueList object by setting the values of the parameters + * passed. Any parameters not provided will be left unchanged. Note that + * item_type is immutable. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Radar\ValueList + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/radar/value_lists/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/RefundService.php b/libraries/Stripe/lib/Service/RefundService.php new file mode 100644 index 00000000000..530bf626f94 --- /dev/null +++ b/libraries/Stripe/lib/Service/RefundService.php @@ -0,0 +1,110 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/refunds', $params, $opts); + } + + /** + * Cancels a refund with a status of requires_action. + * + * You can’t cancel refunds in other states. Only refunds for payment methods that + * require customer action can enter the requires_action state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/refunds/%s/cancel', $id), $params, $opts); + } + + /** + * When you create a new refund, you must specify a Charge or a PaymentIntent + * object on which to create it. + * + * Creating a new refund will refund a charge that has previously been created but + * not yet refunded. Funds will be refunded to the credit or debit card that was + * originally charged. + * + * You can optionally refund only part of a charge. You can do so multiple times, + * until the entire charge has been refunded. + * + * Once entirely refunded, a charge can’t be refunded again. This method will raise + * an error when called on an already-refunded charge, or when trying to refund + * more money than is left on a charge. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/refunds', $params, $opts); + } + + /** + * Retrieves the details of an existing refund. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/refunds/%s', $id), $params, $opts); + } + + /** + * Updates the refund that you specify by setting the values of the passed + * parameters. Any parameters that you don’t provide remain unchanged. + * + * This request only accepts metadata as an argument. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/refunds/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Reporting/ReportRunService.php b/libraries/Stripe/lib/Service/Reporting/ReportRunService.php new file mode 100644 index 00000000000..ad3aec4946e --- /dev/null +++ b/libraries/Stripe/lib/Service/Reporting/ReportRunService.php @@ -0,0 +1,59 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/reporting/report_runs', $params, $opts); + } + + /** + * Creates a new object and begin running the report. (Certain report types require + * a live-mode API key.). + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Reporting\ReportRun + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/reporting/report_runs', $params, $opts); + } + + /** + * Retrieves the details of an existing Report Run. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Reporting\ReportRun + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/reporting/report_runs/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Reporting/ReportTypeService.php b/libraries/Stripe/lib/Service/Reporting/ReportTypeService.php new file mode 100644 index 00000000000..509abf2ea47 --- /dev/null +++ b/libraries/Stripe/lib/Service/Reporting/ReportTypeService.php @@ -0,0 +1,44 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/reporting/report_types', $params, $opts); + } + + /** + * Retrieves the details of a Report Type. (Certain report types require a live-mode API key.). + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Reporting\ReportType + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/reporting/report_types/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Reporting/ReportingServiceFactory.php b/libraries/Stripe/lib/Service/Reporting/ReportingServiceFactory.php new file mode 100644 index 00000000000..6d484aadf91 --- /dev/null +++ b/libraries/Stripe/lib/Service/Reporting/ReportingServiceFactory.php @@ -0,0 +1,27 @@ + + */ + private static $classMap = [ + 'reportRuns' => ReportRunService::class, + 'reportTypes' => ReportTypeService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/ReviewService.php b/libraries/Stripe/lib/Service/ReviewService.php new file mode 100644 index 00000000000..d1ed98c65b5 --- /dev/null +++ b/libraries/Stripe/lib/Service/ReviewService.php @@ -0,0 +1,62 @@ +Review objects that have open set to + * true. The objects are sorted in descending order by creation date, + * with the most recently created object appearing first. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Review> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/reviews', $params, $opts); + } + + /** + * Approves a Review object, closing it and removing it from the list + * of reviews. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Review + */ + public function approve($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/reviews/%s/approve', $id), $params, $opts); + } + + /** + * Retrieves a Review object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Review + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/reviews/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/SetupAttemptService.php b/libraries/Stripe/lib/Service/SetupAttemptService.php new file mode 100644 index 00000000000..90bae0ac05c --- /dev/null +++ b/libraries/Stripe/lib/Service/SetupAttemptService.php @@ -0,0 +1,27 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/setup_attempts', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/SetupIntentService.php b/libraries/Stripe/lib/Service/SetupIntentService.php new file mode 100644 index 00000000000..f6aef67be28 --- /dev/null +++ b/libraries/Stripe/lib/Service/SetupIntentService.php @@ -0,0 +1,150 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/setup_intents', $params, $opts); + } + + /** + * You can cancel a SetupIntent object when it’s in one of these statuses: + * requires_payment_method, requires_confirmation, or + * requires_action. + * + * After you cancel it, setup is abandoned and any operations on the SetupIntent + * fail with an error. You can’t cancel the SetupIntent for a Checkout Session. Expire the Checkout Session + * instead. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/setup_intents/%s/cancel', $id), $params, $opts); + } + + /** + * Confirm that your customer intends to set up the current or provided payment + * method. For example, you would confirm a SetupIntent when a customer hits the + * “Save” button on a payment method management page on your website. + * + * If the selected payment method does not require any additional steps from the + * customer, the SetupIntent will transition to the succeeded status. + * + * Otherwise, it will transition to the requires_action status and + * suggest additional actions via next_action. If setup fails, the + * SetupIntent will transition to the requires_payment_method status + * or the canceled status if the confirmation limit is reached. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent + */ + public function confirm($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/setup_intents/%s/confirm', $id), $params, $opts); + } + + /** + * Creates a SetupIntent object. + * + * After you create the SetupIntent, attach a payment method and confirm it to collect any required + * permissions to charge the payment method later. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/setup_intents', $params, $opts); + } + + /** + * Retrieves the details of a SetupIntent that has previously been created. + * + * Client-side retrieval using a publishable key is allowed when the + * client_secret is provided in the query string. + * + * When retrieved with a publishable key, only a subset of properties will be + * returned. Please refer to the SetupIntent + * object reference for more details. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/setup_intents/%s', $id), $params, $opts); + } + + /** + * Updates a SetupIntent object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/setup_intents/%s', $id), $params, $opts); + } + + /** + * Verifies microdeposits on a SetupIntent object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent + */ + public function verifyMicrodeposits($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/setup_intents/%s/verify_microdeposits', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/ShippingRateService.php b/libraries/Stripe/lib/Service/ShippingRateService.php new file mode 100644 index 00000000000..01e8db95ae9 --- /dev/null +++ b/libraries/Stripe/lib/Service/ShippingRateService.php @@ -0,0 +1,74 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/shipping_rates', $params, $opts); + } + + /** + * Creates a new shipping rate object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ShippingRate + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/shipping_rates', $params, $opts); + } + + /** + * Returns the shipping rate object with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ShippingRate + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/shipping_rates/%s', $id), $params, $opts); + } + + /** + * Updates an existing shipping rate object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ShippingRate + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/shipping_rates/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Sigma/ScheduledQueryRunService.php b/libraries/Stripe/lib/Service/Sigma/ScheduledQueryRunService.php new file mode 100644 index 00000000000..6958358df8f --- /dev/null +++ b/libraries/Stripe/lib/Service/Sigma/ScheduledQueryRunService.php @@ -0,0 +1,43 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/sigma/scheduled_query_runs', $params, $opts); + } + + /** + * Retrieves the details of an scheduled query run. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Sigma\ScheduledQueryRun + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/sigma/scheduled_query_runs/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Sigma/SigmaServiceFactory.php b/libraries/Stripe/lib/Service/Sigma/SigmaServiceFactory.php new file mode 100644 index 00000000000..ce35269a469 --- /dev/null +++ b/libraries/Stripe/lib/Service/Sigma/SigmaServiceFactory.php @@ -0,0 +1,25 @@ + + */ + private static $classMap = [ + 'scheduledQueryRuns' => ScheduledQueryRunService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/SourceService.php b/libraries/Stripe/lib/Service/SourceService.php new file mode 100644 index 00000000000..8598cc6699e --- /dev/null +++ b/libraries/Stripe/lib/Service/SourceService.php @@ -0,0 +1,116 @@ + + */ + public function allSourceTransactions($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/sources/%s/source_transactions', $id), $params, $opts); + } + + /** + * Creates a new source object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/sources', $params, $opts); + } + + /** + * Delete a specified source for a given customer. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source + */ + public function detach($parentId, $id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/customers/%s/sources/%s', $parentId, $id), $params, $opts); + } + + /** + * Retrieves an existing source object. Supply the unique source ID from a source + * creation request and EDD\Vendor\Stripe will return the corresponding up-to-date source + * object information. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/sources/%s', $id), $params, $opts); + } + + /** + * Updates the specified source by setting the values of the parameters passed. Any + * parameters not provided will be left unchanged. + * + * This request accepts the metadata and owner as + * arguments. It is also possible to update type specific information for selected + * payment methods. Please refer to our payment method + * guides for more detail. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/sources/%s', $id), $params, $opts); + } + + /** + * Verify a given source. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source + */ + public function verify($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/sources/%s/verify', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/SubscriptionItemService.php b/libraries/Stripe/lib/Service/SubscriptionItemService.php new file mode 100644 index 00000000000..3ee34c64b9a --- /dev/null +++ b/libraries/Stripe/lib/Service/SubscriptionItemService.php @@ -0,0 +1,155 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/subscription_items', $params, $opts); + } + + /** + * For the specified subscription item, returns a list of summary objects. Each + * object in the list provides usage information that’s been summarized from + * multiple usage records and over a subscription billing period (e.g., 15 usage + * records in the month of September). + * + * The list is sorted in reverse-chronological order (newest first). The first list + * item represents the most current usage period that hasn’t ended yet. Since new + * usage records can still be added, the returned summary information for the + * subscription item’s ID should be seen as unstable until the subscription billing + * period ends. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\UsageRecordSummary> + */ + public function allUsageRecordSummaries($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/subscription_items/%s/usage_record_summaries', $parentId), $params, $opts); + } + + /** + * Adds a new item to an existing subscription. No existing items will be changed + * or replaced. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionItem + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/subscription_items', $params, $opts); + } + + /** + * Creates a usage record for a specified subscription item and date, and fills it + * with a quantity. + * + * Usage records provide quantity information that EDD\Vendor\Stripe uses to + * track how much a customer is using your service. With usage information and the + * pricing model set up by the metered + * billing plan, EDD\Vendor\Stripe helps you send accurate invoices to your customers. + * + * The default calculation for usage is to add up all the quantity + * values of the usage records within a billing period. You can change this default + * behavior with the billing plan’s aggregate_usage parameter. When + * there is more than one usage record with the same timestamp, EDD\Vendor\Stripe adds the + * quantity values together. In most cases, this is the desired + * resolution, however, you can change this behavior with the action + * parameter. + * + * The default pricing model for metered billing is per-unit pricing. + * For finer granularity, you can configure metered billing to have a tiered pricing + * model. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\UsageRecord + */ + public function createUsageRecord($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/subscription_items/%s/usage_records', $parentId), $params, $opts); + } + + /** + * Deletes an item from the subscription. Removing a subscription item from a + * subscription will not cancel the subscription. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionItem + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/subscription_items/%s', $id), $params, $opts); + } + + /** + * Retrieves the subscription item with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionItem + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/subscription_items/%s', $id), $params, $opts); + } + + /** + * Updates the plan or quantity of an item on a current subscription. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionItem + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/subscription_items/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/SubscriptionScheduleService.php b/libraries/Stripe/lib/Service/SubscriptionScheduleService.php new file mode 100644 index 00000000000..9466b9cdc93 --- /dev/null +++ b/libraries/Stripe/lib/Service/SubscriptionScheduleService.php @@ -0,0 +1,117 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/subscription_schedules', $params, $opts); + } + + /** + * Cancels a subscription schedule and its associated subscription immediately (if + * the subscription schedule has an active subscription). A subscription schedule + * can only be canceled if its status is not_started or + * active. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/subscription_schedules/%s/cancel', $id), $params, $opts); + } + + /** + * Creates a new subscription schedule object. Each customer can have up to 500 + * active or scheduled subscriptions. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/subscription_schedules', $params, $opts); + } + + /** + * Releases the subscription schedule immediately, which will stop scheduling of + * its phases, but leave any existing subscription in place. A schedule can only be + * released if its status is not_started or active. If + * the subscription schedule is currently associated with a subscription, releasing + * it will remove its subscription property and set the subscription’s + * ID to the released_subscription property. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule + */ + public function release($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/subscription_schedules/%s/release', $id), $params, $opts); + } + + /** + * Retrieves the details of an existing subscription schedule. You only need to + * supply the unique subscription schedule identifier that was returned upon + * subscription schedule creation. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/subscription_schedules/%s', $id), $params, $opts); + } + + /** + * Updates an existing subscription schedule. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/subscription_schedules/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/SubscriptionService.php b/libraries/Stripe/lib/Service/SubscriptionService.php new file mode 100644 index 00000000000..1f409f2d00a --- /dev/null +++ b/libraries/Stripe/lib/Service/SubscriptionService.php @@ -0,0 +1,224 @@ +status=canceled. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Subscription> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/subscriptions', $params, $opts); + } + + /** + * Cancels a customer’s subscription immediately. The customer will not be charged + * again for the subscription. + * + * Note, however, that any pending invoice items that you’ve created will still be + * charged for at the end of the period, unless manually deleted. If you’ve set the subscription to cancel + * at the end of the period, any pending prorations will also be left in place and + * collected at the end of the period. But if the subscription is set to cancel + * immediately, pending prorations will be removed. + * + * By default, upon subscription cancellation, EDD\Vendor\Stripe will stop automatic + * collection of all finalized invoices for the customer. This is intended to + * prevent unexpected payment attempts after the customer has canceled a + * subscription. However, you can resume automatic collection of the invoices + * manually after subscription cancellation to have us proceed. Or, you could check + * for unpaid invoices before allowing the customer to cancel the subscription at + * all. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/subscriptions/%s', $id), $params, $opts); + } + + /** + * Creates a new subscription on an existing customer. Each customer can have up to + * 500 active or scheduled subscriptions. + * + * When you create a subscription with + * collection_method=charge_automatically, the first invoice is + * finalized as part of the request. The payment_behavior parameter + * determines the exact behavior of the initial payment. + * + * To start subscriptions where the first invoice always begins in a + * draft status, use subscription + * schedules instead. Schedules provide the flexibility to model more complex + * billing configurations that change over time. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/subscriptions', $params, $opts); + } + + /** + * Removes the currently applied discount on a subscription. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Discount + */ + public function deleteDiscount($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/subscriptions/%s/discount', $id), $params, $opts); + } + + /** + * Initiates resumption of a paused subscription, optionally resetting the billing + * cycle anchor and creating prorations. If a resumption invoice is generated, it + * must be paid or marked uncollectible before the subscription will be unpaused. + * If payment succeeds the subscription will become active, and if + * payment fails the subscription will be past_due. The resumption + * invoice will void automatically if not paid by the expiration date. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription + */ + public function resume($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/subscriptions/%s/resume', $id), $params, $opts); + } + + /** + * Retrieves the subscription with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/subscriptions/%s', $id), $params, $opts); + } + + /** + * Search for subscriptions you’ve previously created using Stripe’s Search Query Language. Don’t use + * search in read-after-write flows where strict consistency is necessary. Under + * normal operating conditions, data is searchable in less than a minute. + * Occasionally, propagation of new or updated data can be up to an hour behind + * during outages. Search functionality is not available to merchants in India. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Subscription> + */ + public function search($params = null, $opts = null) + { + return $this->requestSearchResult('get', '/v1/subscriptions/search', $params, $opts); + } + + /** + * Updates an existing subscription to match the specified parameters. When + * changing prices or quantities, we optionally prorate the price we charge next + * month to make up for any price changes. To preview how the proration is + * calculated, use the create + * preview endpoint. + * + * By default, we prorate subscription changes. For example, if a customer signs up + * on May 1 for a 100 price, they’ll be billed + * 100 immediately. If on May 15 they switch to a + * 200 price, then on June 1 they’ll be billed + * 250 (200 for a renewal of her + * subscription, plus a 50 prorating adjustment for half of + * the previous month’s 100 difference). Similarly, a + * downgrade generates a credit that is applied to the next invoice. We also + * prorate when you make quantity changes. + * + * Switching prices does not normally change the billing date or generate an + * immediate charge unless: + * + *
    • The billing interval is changed (for example, from monthly to + * yearly).
    • The subscription moves from free to paid.
    • A trial + * starts or ends.
    + * + * In these cases, we apply a credit for the unused time on the previous price, + * immediately charge the customer using the new price, and reset the billing date. + * Learn about how EDD\Vendor\Stripe + * immediately attempts payment for subscription changes. + * + * If you want to charge for an upgrade immediately, pass + * proration_behavior as always_invoice to create + * prorations, automatically invoice the customer for those proration adjustments, + * and attempt to collect payment. If you pass create_prorations, the + * prorations are created but not automatically invoiced. If you want to bill the + * customer for the prorations before the subscription’s renewal date, you need to + * manually invoice the customer. + * + * If you don’t want to prorate, set the proration_behavior option to + * none. With this option, the customer is billed + * 100 on May 1 and 200 on June 1. + * Similarly, if you set proration_behavior to none when + * switching between different billing intervals (for example, from monthly to + * yearly), we don’t generate any credits for the old subscription’s unused time. + * We still reset the billing date and bill immediately for the new subscription. + * + * Updating the quantity on a subscription many times in an hour may result in rate limiting. If you need to bill for a frequently + * changing quantity, consider integrating usage-based billing instead. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/subscriptions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Tax/CalculationService.php b/libraries/Stripe/lib/Service/Tax/CalculationService.php new file mode 100644 index 00000000000..6fe6e4fb210 --- /dev/null +++ b/libraries/Stripe/lib/Service/Tax/CalculationService.php @@ -0,0 +1,62 @@ + + */ + public function allLineItems($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/tax/calculations/%s/line_items', $id), $params, $opts); + } + + /** + * Calculates tax based on the input and returns a Tax Calculation + * object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Calculation + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/tax/calculations', $params, $opts); + } + + /** + * Retrieves a Tax Calculation object, if the calculation hasn’t + * expired. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Calculation + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/tax/calculations/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Tax/RegistrationService.php b/libraries/Stripe/lib/Service/Tax/RegistrationService.php new file mode 100644 index 00000000000..1a11326a25e --- /dev/null +++ b/libraries/Stripe/lib/Service/Tax/RegistrationService.php @@ -0,0 +1,77 @@ +Registration objects. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Tax\Registration> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/tax/registrations', $params, $opts); + } + + /** + * Creates a new Tax Registration object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Registration + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/tax/registrations', $params, $opts); + } + + /** + * Returns a Tax Registration object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Registration + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/tax/registrations/%s', $id), $params, $opts); + } + + /** + * Updates an existing Tax Registration object. + * + * A registration cannot be deleted after it has been created. If you wish to end a + * registration you may do so by setting expires_at. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Registration + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/tax/registrations/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Tax/SettingsService.php b/libraries/Stripe/lib/Service/Tax/SettingsService.php new file mode 100644 index 00000000000..803a71c9553 --- /dev/null +++ b/libraries/Stripe/lib/Service/Tax/SettingsService.php @@ -0,0 +1,43 @@ +Settings for a merchant. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Settings + */ + public function retrieve($params = null, $opts = null) + { + return $this->request('get', '/v1/tax/settings', $params, $opts); + } + + /** + * Updates Tax Settings parameters used in tax calculations. All + * parameters are editable but none can be removed once set. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Settings + */ + public function update($params = null, $opts = null) + { + return $this->request('post', '/v1/tax/settings', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Tax/TaxServiceFactory.php b/libraries/Stripe/lib/Service/Tax/TaxServiceFactory.php new file mode 100644 index 00000000000..b0e54710a5d --- /dev/null +++ b/libraries/Stripe/lib/Service/Tax/TaxServiceFactory.php @@ -0,0 +1,31 @@ + + */ + private static $classMap = [ + 'calculations' => CalculationService::class, + 'registrations' => RegistrationService::class, + 'settings' => SettingsService::class, + 'transactions' => TransactionService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/Tax/TransactionService.php b/libraries/Stripe/lib/Service/Tax/TransactionService.php new file mode 100644 index 00000000000..a009bf3653a --- /dev/null +++ b/libraries/Stripe/lib/Service/Tax/TransactionService.php @@ -0,0 +1,75 @@ + + */ + public function allLineItems($id, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/tax/transactions/%s/line_items', $id), $params, $opts); + } + + /** + * Creates a Tax Transaction from a calculation, if that calculation hasn’t + * expired. Calculations expire after 90 days. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Transaction + */ + public function createFromCalculation($params = null, $opts = null) + { + return $this->request('post', '/v1/tax/transactions/create_from_calculation', $params, $opts); + } + + /** + * Partially or fully reverses a previously created Transaction. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Transaction + */ + public function createReversal($params = null, $opts = null) + { + return $this->request('post', '/v1/tax/transactions/create_reversal', $params, $opts); + } + + /** + * Retrieves a Tax Transaction object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Transaction + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/tax/transactions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TaxCodeService.php b/libraries/Stripe/lib/Service/TaxCodeService.php new file mode 100644 index 00000000000..700468eaae0 --- /dev/null +++ b/libraries/Stripe/lib/Service/TaxCodeService.php @@ -0,0 +1,45 @@ +all tax codes + * available to add to Products in order to allow specific tax calculations. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TaxCode> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/tax_codes', $params, $opts); + } + + /** + * Retrieves the details of an existing tax code. Supply the unique tax code ID and + * EDD\Vendor\Stripe will return the corresponding tax code information. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxCode + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/tax_codes/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TaxIdService.php b/libraries/Stripe/lib/Service/TaxIdService.php new file mode 100644 index 00000000000..ee09e9c697f --- /dev/null +++ b/libraries/Stripe/lib/Service/TaxIdService.php @@ -0,0 +1,74 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/tax_ids', $params, $opts); + } + + /** + * Creates a new account or customer tax_id object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/tax_ids', $params, $opts); + } + + /** + * Deletes an existing account or customer tax_id object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/tax_ids/%s', $id), $params, $opts); + } + + /** + * Retrieves an account or customer tax_id object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/tax_ids/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TaxRateService.php b/libraries/Stripe/lib/Service/TaxRateService.php new file mode 100644 index 00000000000..957687135a0 --- /dev/null +++ b/libraries/Stripe/lib/Service/TaxRateService.php @@ -0,0 +1,75 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/tax_rates', $params, $opts); + } + + /** + * Creates a new tax rate. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxRate + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/tax_rates', $params, $opts); + } + + /** + * Retrieves a tax rate with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxRate + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/tax_rates/%s', $id), $params, $opts); + } + + /** + * Updates an existing tax rate. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxRate + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/tax_rates/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Terminal/ConfigurationService.php b/libraries/Stripe/lib/Service/Terminal/ConfigurationService.php new file mode 100644 index 00000000000..92de11ce89b --- /dev/null +++ b/libraries/Stripe/lib/Service/Terminal/ConfigurationService.php @@ -0,0 +1,90 @@ +Configuration objects. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Terminal\Configuration> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/terminal/configurations', $params, $opts); + } + + /** + * Creates a new Configuration object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Configuration + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/terminal/configurations', $params, $opts); + } + + /** + * Deletes a Configuration object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Configuration + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/terminal/configurations/%s', $id), $params, $opts); + } + + /** + * Retrieves a Configuration object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Configuration + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/terminal/configurations/%s', $id), $params, $opts); + } + + /** + * Updates a new Configuration object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Configuration + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/terminal/configurations/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Terminal/ConnectionTokenService.php b/libraries/Stripe/lib/Service/Terminal/ConnectionTokenService.php new file mode 100644 index 00000000000..0ddfc3a24f0 --- /dev/null +++ b/libraries/Stripe/lib/Service/Terminal/ConnectionTokenService.php @@ -0,0 +1,29 @@ +request('post', '/v1/terminal/connection_tokens', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Terminal/LocationService.php b/libraries/Stripe/lib/Service/Terminal/LocationService.php new file mode 100644 index 00000000000..e6b82a4ffa1 --- /dev/null +++ b/libraries/Stripe/lib/Service/Terminal/LocationService.php @@ -0,0 +1,93 @@ +Location objects. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Terminal\Location> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/terminal/locations', $params, $opts); + } + + /** + * Creates a new Location object. For further details, including which + * address fields are required in each country, see the Manage locations guide. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Location + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/terminal/locations', $params, $opts); + } + + /** + * Deletes a Location object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Location + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/terminal/locations/%s', $id), $params, $opts); + } + + /** + * Retrieves a Location object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Location + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/terminal/locations/%s', $id), $params, $opts); + } + + /** + * Updates a Location object by setting the values of the parameters + * passed. Any parameters not provided will be left unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Location + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/terminal/locations/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Terminal/ReaderService.php b/libraries/Stripe/lib/Service/Terminal/ReaderService.php new file mode 100644 index 00000000000..5015c4903de --- /dev/null +++ b/libraries/Stripe/lib/Service/Terminal/ReaderService.php @@ -0,0 +1,171 @@ +Reader objects. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Terminal\Reader> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/terminal/readers', $params, $opts); + } + + /** + * Cancels the current reader action. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function cancelAction($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/terminal/readers/%s/cancel_action', $id), $params, $opts); + } + + /** + * Creates a new Reader object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/terminal/readers', $params, $opts); + } + + /** + * Deletes a Reader object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/terminal/readers/%s', $id), $params, $opts); + } + + /** + * Initiates a payment flow on a Reader. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function processPaymentIntent($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/terminal/readers/%s/process_payment_intent', $id), $params, $opts); + } + + /** + * Initiates a setup intent flow on a Reader. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function processSetupIntent($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/terminal/readers/%s/process_setup_intent', $id), $params, $opts); + } + + /** + * Initiates a refund on a Reader. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function refundPayment($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/terminal/readers/%s/refund_payment', $id), $params, $opts); + } + + /** + * Retrieves a Reader object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/terminal/readers/%s', $id), $params, $opts); + } + + /** + * Sets reader display to show cart details. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function setReaderDisplay($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/terminal/readers/%s/set_reader_display', $id), $params, $opts); + } + + /** + * Updates a Reader object by setting the values of the parameters + * passed. Any parameters not provided will be left unchanged. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/terminal/readers/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Terminal/TerminalServiceFactory.php b/libraries/Stripe/lib/Service/Terminal/TerminalServiceFactory.php new file mode 100644 index 00000000000..5a0e4129b77 --- /dev/null +++ b/libraries/Stripe/lib/Service/Terminal/TerminalServiceFactory.php @@ -0,0 +1,31 @@ + + */ + private static $classMap = [ + 'configurations' => ConfigurationService::class, + 'connectionTokens' => ConnectionTokenService::class, + 'locations' => LocationService::class, + 'readers' => ReaderService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/ConfirmationTokenService.php b/libraries/Stripe/lib/Service/TestHelpers/ConfirmationTokenService.php new file mode 100644 index 00000000000..0e0c80881da --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/ConfirmationTokenService.php @@ -0,0 +1,27 @@ +request('post', '/v1/test_helpers/confirmation_tokens', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/CustomerService.php b/libraries/Stripe/lib/Service/TestHelpers/CustomerService.php new file mode 100644 index 00000000000..6e965f06dd1 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/CustomerService.php @@ -0,0 +1,28 @@ +request('post', $this->buildPath('/v1/test_helpers/customers/%s/fund_cash_balance', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Issuing/AuthorizationService.php b/libraries/Stripe/lib/Service/TestHelpers/Issuing/AuthorizationService.php new file mode 100644 index 00000000000..fdf17938020 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Issuing/AuthorizationService.php @@ -0,0 +1,108 @@ +request('post', $this->buildPath('/v1/test_helpers/issuing/authorizations/%s/capture', $id), $params, $opts); + } + + /** + * Create a test-mode authorization. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/test_helpers/issuing/authorizations', $params, $opts); + } + + /** + * Expire a test-mode Authorization. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function expire($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/authorizations/%s/expire', $id), $params, $opts); + } + + /** + * Finalize the amount on an Authorization prior to capture, when the initial + * authorization was for an estimated amount. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function finalizeAmount($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/authorizations/%s/finalize_amount', $id), $params, $opts); + } + + /** + * Increment a test-mode Authorization. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function increment($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/authorizations/%s/increment', $id), $params, $opts); + } + + /** + * Reverse a test-mode Authorization. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Authorization + */ + public function reverse($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/authorizations/%s/reverse', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Issuing/CardService.php b/libraries/Stripe/lib/Service/TestHelpers/Issuing/CardService.php new file mode 100644 index 00000000000..d5bf2b7ec43 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Issuing/CardService.php @@ -0,0 +1,80 @@ +Card object to + * delivered. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card + */ + public function deliverCard($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/cards/%s/shipping/deliver', $id), $params, $opts); + } + + /** + * Updates the shipping status of the specified Issuing Card object to + * failure. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card + */ + public function failCard($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/cards/%s/shipping/fail', $id), $params, $opts); + } + + /** + * Updates the shipping status of the specified Issuing Card object to + * returned. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card + */ + public function returnCard($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/cards/%s/shipping/return', $id), $params, $opts); + } + + /** + * Updates the shipping status of the specified Issuing Card object to + * shipped. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Card + */ + public function shipCard($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/cards/%s/shipping/ship', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Issuing/IssuingServiceFactory.php b/libraries/Stripe/lib/Service/TestHelpers/Issuing/IssuingServiceFactory.php new file mode 100644 index 00000000000..740533e29a2 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Issuing/IssuingServiceFactory.php @@ -0,0 +1,31 @@ + + */ + private static $classMap = [ + 'authorizations' => AuthorizationService::class, + 'cards' => CardService::class, + 'personalizationDesigns' => PersonalizationDesignService::class, + 'transactions' => TransactionService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Issuing/PersonalizationDesignService.php b/libraries/Stripe/lib/Service/TestHelpers/Issuing/PersonalizationDesignService.php new file mode 100644 index 00000000000..cbfae03e54f --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Issuing/PersonalizationDesignService.php @@ -0,0 +1,63 @@ +status of the specified testmode personalization design + * object to active. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign + */ + public function activate($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/personalization_designs/%s/activate', $id), $params, $opts); + } + + /** + * Updates the status of the specified testmode personalization design + * object to inactive. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign + */ + public function deactivate($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/personalization_designs/%s/deactivate', $id), $params, $opts); + } + + /** + * Updates the status of the specified testmode personalization design + * object to rejected. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\PersonalizationDesign + */ + public function reject($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/personalization_designs/%s/reject', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Issuing/TransactionService.php b/libraries/Stripe/lib/Service/TestHelpers/Issuing/TransactionService.php new file mode 100644 index 00000000000..3ea7a4333ac --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Issuing/TransactionService.php @@ -0,0 +1,58 @@ +request('post', '/v1/test_helpers/issuing/transactions/create_force_capture', $params, $opts); + } + + /** + * Allows the user to refund an arbitrary amount, also known as a unlinked refund. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Transaction + */ + public function createUnlinkedRefund($params = null, $opts = null) + { + return $this->request('post', '/v1/test_helpers/issuing/transactions/create_unlinked_refund', $params, $opts); + } + + /** + * Refund a test-mode Transaction. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Issuing\Transaction + */ + public function refund($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/issuing/transactions/%s/refund', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/RefundService.php b/libraries/Stripe/lib/Service/TestHelpers/RefundService.php new file mode 100644 index 00000000000..30aa35e5908 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/RefundService.php @@ -0,0 +1,28 @@ +requires_action. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Refund + */ + public function expire($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/refunds/%s/expire', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Terminal/ReaderService.php b/libraries/Stripe/lib/Service/TestHelpers/Terminal/ReaderService.php new file mode 100644 index 00000000000..320943f3770 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Terminal/ReaderService.php @@ -0,0 +1,29 @@ +request('post', $this->buildPath('/v1/test_helpers/terminal/readers/%s/present_payment_method', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Terminal/TerminalServiceFactory.php b/libraries/Stripe/lib/Service/TestHelpers/Terminal/TerminalServiceFactory.php new file mode 100644 index 00000000000..37301cb8b05 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Terminal/TerminalServiceFactory.php @@ -0,0 +1,25 @@ + + */ + private static $classMap = [ + 'readers' => ReaderService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/TestClockService.php b/libraries/Stripe/lib/Service/TestHelpers/TestClockService.php new file mode 100644 index 00000000000..0e7e13a2fd0 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/TestClockService.php @@ -0,0 +1,91 @@ +Ready. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TestHelpers\TestClock + */ + public function advance($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/test_clocks/%s/advance', $id), $params, $opts); + } + + /** + * Returns a list of your test clocks. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TestHelpers\TestClock> + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/test_helpers/test_clocks', $params, $opts); + } + + /** + * Creates a new test clock that can be attached to new customers and quotes. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TestHelpers\TestClock + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/test_helpers/test_clocks', $params, $opts); + } + + /** + * Deletes a test clock. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TestHelpers\TestClock + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/test_helpers/test_clocks/%s', $id), $params, $opts); + } + + /** + * Retrieves a test clock. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TestHelpers\TestClock + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/test_helpers/test_clocks/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/TestHelpersServiceFactory.php b/libraries/Stripe/lib/Service/TestHelpers/TestHelpersServiceFactory.php new file mode 100644 index 00000000000..c1ed73af158 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/TestHelpersServiceFactory.php @@ -0,0 +1,37 @@ + + */ + private static $classMap = [ + 'confirmationTokens' => ConfirmationTokenService::class, + 'customers' => CustomerService::class, + 'issuing' => Issuing\IssuingServiceFactory::class, + 'refunds' => RefundService::class, + 'terminal' => Terminal\TerminalServiceFactory::class, + 'testClocks' => TestClockService::class, + 'treasury' => Treasury\TreasuryServiceFactory::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Treasury/InboundTransferService.php b/libraries/Stripe/lib/Service/TestHelpers/Treasury/InboundTransferService.php new file mode 100644 index 00000000000..a2e2a3db534 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Treasury/InboundTransferService.php @@ -0,0 +1,66 @@ +failed + * status. The InboundTransfer must already be in the processing + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer + */ + public function fail($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/inbound_transfers/%s/fail', $id), $params, $opts); + } + + /** + * Marks the test mode InboundTransfer object as returned and links the + * InboundTransfer to a ReceivedDebit. The InboundTransfer must already be in the + * succeeded state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer + */ + public function returnInboundTransfer($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/inbound_transfers/%s/return', $id), $params, $opts); + } + + /** + * Transitions a test mode created InboundTransfer to the succeeded + * status. The InboundTransfer must already be in the processing + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer + */ + public function succeed($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/inbound_transfers/%s/succeed', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Treasury/OutboundPaymentService.php b/libraries/Stripe/lib/Service/TestHelpers/Treasury/OutboundPaymentService.php new file mode 100644 index 00000000000..28053d4de6e --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Treasury/OutboundPaymentService.php @@ -0,0 +1,84 @@ +failed + * status. The OutboundPayment must already be in the processing + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment + */ + public function fail($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/outbound_payments/%s/fail', $id), $params, $opts); + } + + /** + * Transitions a test mode created OutboundPayment to the posted + * status. The OutboundPayment must already be in the processing + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment + */ + public function post($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/outbound_payments/%s/post', $id), $params, $opts); + } + + /** + * Transitions a test mode created OutboundPayment to the returned + * status. The OutboundPayment must already be in the processing + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment + */ + public function returnOutboundPayment($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/outbound_payments/%s/return', $id), $params, $opts); + } + + /** + * Updates a test mode created OutboundPayment with tracking details. The + * OutboundPayment must not be cancelable, and cannot be in the + * canceled or failed states. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/outbound_payments/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Treasury/OutboundTransferService.php b/libraries/Stripe/lib/Service/TestHelpers/Treasury/OutboundTransferService.php new file mode 100644 index 00000000000..03486f83a1e --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Treasury/OutboundTransferService.php @@ -0,0 +1,84 @@ +failed + * status. The OutboundTransfer must already be in the processing + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer + */ + public function fail($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/outbound_transfers/%s/fail', $id), $params, $opts); + } + + /** + * Transitions a test mode created OutboundTransfer to the posted + * status. The OutboundTransfer must already be in the processing + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer + */ + public function post($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/outbound_transfers/%s/post', $id), $params, $opts); + } + + /** + * Transitions a test mode created OutboundTransfer to the returned + * status. The OutboundTransfer must already be in the processing + * state. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer + */ + public function returnOutboundTransfer($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/outbound_transfers/%s/return', $id), $params, $opts); + } + + /** + * Updates a test mode created OutboundTransfer with tracking details. The + * OutboundTransfer must not be cancelable, and cannot be in the + * canceled or failed states. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/test_helpers/treasury/outbound_transfers/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Treasury/ReceivedCreditService.php b/libraries/Stripe/lib/Service/TestHelpers/Treasury/ReceivedCreditService.php new file mode 100644 index 00000000000..a6803860523 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Treasury/ReceivedCreditService.php @@ -0,0 +1,29 @@ +request('post', '/v1/test_helpers/treasury/received_credits', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Treasury/ReceivedDebitService.php b/libraries/Stripe/lib/Service/TestHelpers/Treasury/ReceivedDebitService.php new file mode 100644 index 00000000000..2f90a39955c --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Treasury/ReceivedDebitService.php @@ -0,0 +1,29 @@ +request('post', '/v1/test_helpers/treasury/received_debits', $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TestHelpers/Treasury/TreasuryServiceFactory.php b/libraries/Stripe/lib/Service/TestHelpers/Treasury/TreasuryServiceFactory.php new file mode 100644 index 00000000000..774bb70ed85 --- /dev/null +++ b/libraries/Stripe/lib/Service/TestHelpers/Treasury/TreasuryServiceFactory.php @@ -0,0 +1,33 @@ + + */ + private static $classMap = [ + 'inboundTransfers' => InboundTransferService::class, + 'outboundPayments' => OutboundPaymentService::class, + 'outboundTransfers' => OutboundTransferService::class, + 'receivedCredits' => ReceivedCreditService::class, + 'receivedDebits' => ReceivedDebitService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/TokenService.php b/libraries/Stripe/lib/Service/TokenService.php new file mode 100644 index 00000000000..6fb0aa00488 --- /dev/null +++ b/libraries/Stripe/lib/Service/TokenService.php @@ -0,0 +1,48 @@ +connected + * account where controller.requirement_collection + * is application, which includes Custom accounts. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Token + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/tokens', $params, $opts); + } + + /** + * Retrieves the token with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Token + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/tokens/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TopupService.php b/libraries/Stripe/lib/Service/TopupService.php new file mode 100644 index 00000000000..8d685bfc359 --- /dev/null +++ b/libraries/Stripe/lib/Service/TopupService.php @@ -0,0 +1,93 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/topups', $params, $opts); + } + + /** + * Cancels a top-up. Only pending top-ups can be canceled. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Topup + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/topups/%s/cancel', $id), $params, $opts); + } + + /** + * Top up the balance of an account. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Topup + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/topups', $params, $opts); + } + + /** + * Retrieves the details of a top-up that has previously been created. Supply the + * unique top-up ID that was returned from your previous request, and EDD\Vendor\Stripe will + * return the corresponding top-up information. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Topup + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/topups/%s', $id), $params, $opts); + } + + /** + * Updates the metadata of a top-up. Other top-up details are not editable by + * design. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Topup + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/topups/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/TransferService.php b/libraries/Stripe/lib/Service/TransferService.php new file mode 100644 index 00000000000..bbfce5c06cf --- /dev/null +++ b/libraries/Stripe/lib/Service/TransferService.php @@ -0,0 +1,165 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/transfers', $params, $opts); + } + + /** + * You can see a list of the reversals belonging to a specific transfer. Note that + * the 10 most recent reversals are always available by default on the transfer + * object. If you need more than those 10, you can use this API method and the + * limit and starting_after parameters to page through + * additional reversals. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TransferReversal> + */ + public function allReversals($parentId, $params = null, $opts = null) + { + return $this->requestCollection('get', $this->buildPath('/v1/transfers/%s/reversals', $parentId), $params, $opts); + } + + /** + * To send funds from your EDD\Vendor\Stripe account to a connected account, you create a new + * transfer object. Your EDD\Vendor\Stripe balance must be able to + * cover the transfer amount, or you’ll receive an “Insufficient Funds” error. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Transfer + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/transfers', $params, $opts); + } + + /** + * When you create a new reversal, you must specify a transfer to create it on. + * + * When reversing transfers, you can optionally reverse part of the transfer. You + * can do so as many times as you wish until the entire transfer has been reversed. + * + * Once entirely reversed, a transfer can’t be reversed again. This method will + * return an error when called on an already-reversed transfer, or when trying to + * reverse more money than is left on a transfer. + * + * @param string $parentId + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TransferReversal + */ + public function createReversal($parentId, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/transfers/%s/reversals', $parentId), $params, $opts); + } + + /** + * Retrieves the details of an existing transfer. Supply the unique transfer ID + * from either a transfer creation request or the transfer list, and EDD\Vendor\Stripe will + * return the corresponding transfer information. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Transfer + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/transfers/%s', $id), $params, $opts); + } + + /** + * By default, you can see the 10 most recent reversals stored directly on the + * transfer object, but you can also retrieve details about a specific reversal + * stored on the transfer. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TransferReversal + */ + public function retrieveReversal($parentId, $id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/transfers/%s/reversals/%s', $parentId, $id), $params, $opts); + } + + /** + * Updates the specified transfer by setting the values of the parameters passed. + * Any parameters not provided will be left unchanged. + * + * This request accepts only metadata as an argument. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Transfer + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/transfers/%s', $id), $params, $opts); + } + + /** + * Updates the specified reversal by setting the values of the parameters passed. + * Any parameters not provided will be left unchanged. + * + * This request only accepts metadata and description as arguments. + * + * @param string $parentId + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TransferReversal + */ + public function updateReversal($parentId, $id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/transfers/%s/reversals/%s', $parentId, $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/CreditReversalService.php b/libraries/Stripe/lib/Service/Treasury/CreditReversalService.php new file mode 100644 index 00000000000..2047cc08a2c --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/CreditReversalService.php @@ -0,0 +1,60 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/credit_reversals', $params, $opts); + } + + /** + * Reverses a ReceivedCredit and creates a CreditReversal object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\CreditReversal + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/treasury/credit_reversals', $params, $opts); + } + + /** + * Retrieves the details of an existing CreditReversal by passing the unique + * CreditReversal ID from either the CreditReversal creation request or + * CreditReversal list. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\CreditReversal + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/credit_reversals/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/DebitReversalService.php b/libraries/Stripe/lib/Service/Treasury/DebitReversalService.php new file mode 100644 index 00000000000..c64780330d7 --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/DebitReversalService.php @@ -0,0 +1,58 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/debit_reversals', $params, $opts); + } + + /** + * Reverses a ReceivedDebit and creates a DebitReversal object. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\DebitReversal + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/treasury/debit_reversals', $params, $opts); + } + + /** + * Retrieves a DebitReversal object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\DebitReversal + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/debit_reversals/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/FinancialAccountService.php b/libraries/Stripe/lib/Service/Treasury/FinancialAccountService.php new file mode 100644 index 00000000000..38bc8fd0aac --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/FinancialAccountService.php @@ -0,0 +1,107 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/financial_accounts', $params, $opts); + } + + /** + * Creates a new FinancialAccount. For now, each connected account can only have + * one FinancialAccount. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccount + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/treasury/financial_accounts', $params, $opts); + } + + /** + * Retrieves the details of a FinancialAccount. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccount + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/financial_accounts/%s', $id), $params, $opts); + } + + /** + * Retrieves Features information associated with the FinancialAccount. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccountFeatures + */ + public function retrieveFeatures($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/financial_accounts/%s/features', $id), $params, $opts); + } + + /** + * Updates the details of a FinancialAccount. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccount + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/treasury/financial_accounts/%s', $id), $params, $opts); + } + + /** + * Updates the Features associated with a FinancialAccount. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccountFeatures + */ + public function updateFeatures($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/treasury/financial_accounts/%s/features', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/InboundTransferService.php b/libraries/Stripe/lib/Service/Treasury/InboundTransferService.php new file mode 100644 index 00000000000..bf4d7842e41 --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/InboundTransferService.php @@ -0,0 +1,74 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/inbound_transfers', $params, $opts); + } + + /** + * Cancels an InboundTransfer. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/treasury/inbound_transfers/%s/cancel', $id), $params, $opts); + } + + /** + * Creates an InboundTransfer. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/treasury/inbound_transfers', $params, $opts); + } + + /** + * Retrieves the details of an existing InboundTransfer. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/inbound_transfers/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/OutboundPaymentService.php b/libraries/Stripe/lib/Service/Treasury/OutboundPaymentService.php new file mode 100644 index 00000000000..5dc5d63faaa --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/OutboundPaymentService.php @@ -0,0 +1,76 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/outbound_payments', $params, $opts); + } + + /** + * Cancel an OutboundPayment. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/treasury/outbound_payments/%s/cancel', $id), $params, $opts); + } + + /** + * Creates an OutboundPayment. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/treasury/outbound_payments', $params, $opts); + } + + /** + * Retrieves the details of an existing OutboundPayment by passing the unique + * OutboundPayment ID from either the OutboundPayment creation request or + * OutboundPayment list. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/outbound_payments/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/OutboundTransferService.php b/libraries/Stripe/lib/Service/Treasury/OutboundTransferService.php new file mode 100644 index 00000000000..02cddbaddca --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/OutboundTransferService.php @@ -0,0 +1,76 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/outbound_transfers', $params, $opts); + } + + /** + * An OutboundTransfer can be canceled if the funds have not yet been paid out. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer + */ + public function cancel($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/treasury/outbound_transfers/%s/cancel', $id), $params, $opts); + } + + /** + * Creates an OutboundTransfer. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/treasury/outbound_transfers', $params, $opts); + } + + /** + * Retrieves the details of an existing OutboundTransfer by passing the unique + * OutboundTransfer ID from either the OutboundTransfer creation request or + * OutboundTransfer list. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/outbound_transfers/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/ReceivedCreditService.php b/libraries/Stripe/lib/Service/Treasury/ReceivedCreditService.php new file mode 100644 index 00000000000..c032c65b5fd --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/ReceivedCreditService.php @@ -0,0 +1,44 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/received_credits', $params, $opts); + } + + /** + * Retrieves the details of an existing ReceivedCredit by passing the unique + * ReceivedCredit ID from the ReceivedCredit list. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\ReceivedCredit + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/received_credits/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/ReceivedDebitService.php b/libraries/Stripe/lib/Service/Treasury/ReceivedDebitService.php new file mode 100644 index 00000000000..caf4a20d9a2 --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/ReceivedDebitService.php @@ -0,0 +1,44 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/received_debits', $params, $opts); + } + + /** + * Retrieves the details of an existing ReceivedDebit by passing the unique + * ReceivedDebit ID from the ReceivedDebit list. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\ReceivedDebit + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/received_debits/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/TransactionEntryService.php b/libraries/Stripe/lib/Service/Treasury/TransactionEntryService.php new file mode 100644 index 00000000000..180f948c4b2 --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/TransactionEntryService.php @@ -0,0 +1,43 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/transaction_entries', $params, $opts); + } + + /** + * Retrieves a TransactionEntry object. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\TransactionEntry + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/transaction_entries/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/TransactionService.php b/libraries/Stripe/lib/Service/Treasury/TransactionService.php new file mode 100644 index 00000000000..c9ae0df1fe0 --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/TransactionService.php @@ -0,0 +1,43 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/treasury/transactions', $params, $opts); + } + + /** + * Retrieves the details of an existing Transaction. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\Transaction + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/treasury/transactions/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/Service/Treasury/TreasuryServiceFactory.php b/libraries/Stripe/lib/Service/Treasury/TreasuryServiceFactory.php new file mode 100644 index 00000000000..1f3b4f7cd7f --- /dev/null +++ b/libraries/Stripe/lib/Service/Treasury/TreasuryServiceFactory.php @@ -0,0 +1,43 @@ + + */ + private static $classMap = [ + 'creditReversals' => CreditReversalService::class, + 'debitReversals' => DebitReversalService::class, + 'financialAccounts' => FinancialAccountService::class, + 'inboundTransfers' => InboundTransferService::class, + 'outboundPayments' => OutboundPaymentService::class, + 'outboundTransfers' => OutboundTransferService::class, + 'receivedCredits' => ReceivedCreditService::class, + 'receivedDebits' => ReceivedDebitService::class, + 'transactionEntries' => TransactionEntryService::class, + 'transactions' => TransactionService::class, + ]; + + protected function getServiceClass($name) + { + return \array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null; + } +} diff --git a/libraries/Stripe/lib/Service/WebhookEndpointService.php b/libraries/Stripe/lib/Service/WebhookEndpointService.php new file mode 100644 index 00000000000..503e5d42b99 --- /dev/null +++ b/libraries/Stripe/lib/Service/WebhookEndpointService.php @@ -0,0 +1,101 @@ + + */ + public function all($params = null, $opts = null) + { + return $this->requestCollection('get', '/v1/webhook_endpoints', $params, $opts); + } + + /** + * A webhook endpoint must have a url and a list of + * enabled_events. You may optionally specify the Boolean + * connect parameter. If set to true, then a Connect webhook endpoint + * that notifies the specified url about events from all connected + * accounts is created; otherwise an account webhook endpoint that notifies the + * specified url only about events from your account is created. You + * can also create webhook endpoints in the webhooks settings + * section of the Dashboard. + * + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\WebhookEndpoint + */ + public function create($params = null, $opts = null) + { + return $this->request('post', '/v1/webhook_endpoints', $params, $opts); + } + + /** + * You can also delete webhook endpoints via the webhook endpoint + * management page of the EDD\Vendor\Stripe dashboard. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\WebhookEndpoint + */ + public function delete($id, $params = null, $opts = null) + { + return $this->request('delete', $this->buildPath('/v1/webhook_endpoints/%s', $id), $params, $opts); + } + + /** + * Retrieves the webhook endpoint with the given ID. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\WebhookEndpoint + */ + public function retrieve($id, $params = null, $opts = null) + { + return $this->request('get', $this->buildPath('/v1/webhook_endpoints/%s', $id), $params, $opts); + } + + /** + * Updates the webhook endpoint. You may edit the url, the list of + * enabled_events, and the status of your endpoint. + * + * @param string $id + * @param null|array $params + * @param null|RequestOptionsArray|\EDD\Vendor\Stripe\Util\RequestOptions $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\WebhookEndpoint + */ + public function update($id, $params = null, $opts = null) + { + return $this->request('post', $this->buildPath('/v1/webhook_endpoints/%s', $id), $params, $opts); + } +} diff --git a/libraries/Stripe/lib/SetupAttempt.php b/libraries/Stripe/lib/SetupAttempt.php new file mode 100644 index 00000000000..f9b690c2eb6 --- /dev/null +++ b/libraries/Stripe/lib/SetupAttempt.php @@ -0,0 +1,49 @@ +application on the SetupIntent at the time of this confirmation. + * @property null|bool $attach_to_self

    If present, the SetupIntent's payment method will be attached to the in-context EDD\Vendor\Stripe Account.

    It can only be used for this EDD\Vendor\Stripe Account’s own money movement flows like InboundTransfer and OutboundTransfers. It cannot be set to true when setting up a PaymentMethod for a Customer, and defaults to false when attaching a PaymentMethod to a Customer.

    + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer The value of customer on the SetupIntent at the time of this confirmation. + * @property null|string[] $flow_directions

    Indicates the directions of money movement for which this payment method is intended to be used.

    Include inbound if you intend to use the payment method as the origin to pull funds from. Include outbound if you intend to use the payment method as the destination to send funds to. You can include both if you intend to use the payment method for both purposes.

    + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string|\EDD\Vendor\Stripe\Account $on_behalf_of The value of on_behalf_of on the SetupIntent at the time of this confirmation. + * @property string|\EDD\Vendor\Stripe\PaymentMethod $payment_method ID of the payment method used with this SetupAttempt. + * @property \EDD\Vendor\Stripe\StripeObject $payment_method_details + * @property null|\EDD\Vendor\Stripe\StripeObject $setup_error The error encountered during this attempt to confirm the SetupIntent, if any. + * @property string|\EDD\Vendor\Stripe\SetupIntent $setup_intent ID of the SetupIntent that this attempt belongs to. + * @property string $status Status of this SetupAttempt, one of requires_confirmation, requires_action, processing, succeeded, failed, or abandoned. + * @property string $usage The value of usage on the SetupIntent at the time of this confirmation, one of off_session or on_session. + */ +class SetupAttempt extends ApiResource +{ + const OBJECT_NAME = 'setup_attempt'; + + /** + * Returns a list of SetupAttempts that associate with a provided SetupIntent. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\SetupAttempt> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/SetupIntent.php b/libraries/Stripe/lib/SetupIntent.php new file mode 100644 index 00000000000..2db72c05303 --- /dev/null +++ b/libraries/Stripe/lib/SetupIntent.php @@ -0,0 +1,215 @@ +PaymentIntents to drive the payment flow. + * + * Create a SetupIntent when you're ready to collect your customer's payment credentials. + * Don't maintain long-lived, unconfirmed SetupIntents because they might not be valid. + * The SetupIntent transitions through multiple statuses as it guides + * you through the setup process. + * + * Successful SetupIntents result in payment credentials that are optimized for future payments. + * For example, cardholders in certain regions might need to be run through + * Strong Customer Authentication during payment method collection + * to streamline later off-session payments. + * If you use the SetupIntent with a Customer, + * it automatically attaches the resulting payment method to that Customer after successful setup. + * We recommend using SetupIntents or setup_future_usage on + * PaymentIntents to save payment methods to prevent saving invalid or unoptimized payment methods. + * + * By using SetupIntents, you can reduce friction for your customers, even as regulations change over time. + * + * Related guide: Setup Intents API + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string|\EDD\Vendor\Stripe\Application $application ID of the Connect application that created the SetupIntent. + * @property null|bool $attach_to_self

    If present, the SetupIntent's payment method will be attached to the in-context EDD\Vendor\Stripe Account.

    It can only be used for this EDD\Vendor\Stripe Account’s own money movement flows like InboundTransfer and OutboundTransfers. It cannot be set to true when setting up a PaymentMethod for a Customer, and defaults to false when attaching a PaymentMethod to a Customer.

    + * @property null|\EDD\Vendor\Stripe\StripeObject $automatic_payment_methods Settings for dynamic payment methods compatible with this Setup Intent + * @property null|string $cancellation_reason Reason for cancellation of this SetupIntent, one of abandoned, requested_by_customer, or duplicate. + * @property null|string $client_secret

    The client secret of this SetupIntent. Used for client-side retrieval using a publishable key.

    The client secret can be used to complete payment setup from your frontend. It should not be stored, logged, or exposed to anyone other than the customer. Make sure that you have TLS enabled on any page that includes the client secret.

    + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer

    ID of the Customer this SetupIntent belongs to, if one exists.

    If present, the SetupIntent's payment method will be attached to the Customer on successful setup. Payment methods attached to other Customers cannot be used with this SetupIntent.

    + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|string[] $flow_directions

    Indicates the directions of money movement for which this payment method is intended to be used.

    Include inbound if you intend to use the payment method as the origin to pull funds from. Include outbound if you intend to use the payment method as the destination to send funds to. You can include both if you intend to use the payment method for both purposes.

    + * @property null|\EDD\Vendor\Stripe\StripeObject $last_setup_error The error encountered in the previous SetupIntent confirmation. + * @property null|string|\EDD\Vendor\Stripe\SetupAttempt $latest_attempt The most recent SetupAttempt for this SetupIntent. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string|\EDD\Vendor\Stripe\Mandate $mandate ID of the multi use Mandate generated by the SetupIntent. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $next_action If present, this property tells you what actions you need to take in order for your customer to continue payment setup. + * @property null|string|\EDD\Vendor\Stripe\Account $on_behalf_of The account (if any) for which the setup is intended. + * @property null|string|\EDD\Vendor\Stripe\PaymentMethod $payment_method ID of the payment method used with this SetupIntent. If the payment method is card_present and isn't a digital wallet, then the generated_card associated with the latest_attempt is attached to the Customer instead. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_configuration_details Information about the payment method configuration used for this Setup Intent. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_method_options Payment method-specific configuration for this SetupIntent. + * @property string[] $payment_method_types The list of payment method types (e.g. card) that this SetupIntent is allowed to set up. + * @property null|string|\EDD\Vendor\Stripe\Mandate $single_use_mandate ID of the single_use Mandate generated by the SetupIntent. + * @property string $status Status of this SetupIntent, one of requires_payment_method, requires_confirmation, requires_action, processing, canceled, or succeeded. + * @property string $usage

    Indicates how the payment method is intended to be used in the future.

    Use on_session if you intend to only reuse the payment method when the customer is in your checkout flow. Use off_session if your customer may or may not be in your checkout flow. If not provided, this value defaults to off_session.

    + */ +class SetupIntent extends ApiResource +{ + const OBJECT_NAME = 'setup_intent'; + + use ApiOperations\Update; + + const CANCELLATION_REASON_ABANDONED = 'abandoned'; + const CANCELLATION_REASON_DUPLICATE = 'duplicate'; + const CANCELLATION_REASON_REQUESTED_BY_CUSTOMER = 'requested_by_customer'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_PROCESSING = 'processing'; + const STATUS_REQUIRES_ACTION = 'requires_action'; + const STATUS_REQUIRES_CONFIRMATION = 'requires_confirmation'; + const STATUS_REQUIRES_PAYMENT_METHOD = 'requires_payment_method'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * Creates a SetupIntent object. + * + * After you create the SetupIntent, attach a payment method and confirm it to collect any required + * permissions to charge the payment method later. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of SetupIntents. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\SetupIntent> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a SetupIntent that has previously been created. + * + * Client-side retrieval using a publishable key is allowed when the + * client_secret is provided in the query string. + * + * When retrieved with a publishable key, only a subset of properties will be + * returned. Please refer to the SetupIntent + * object reference for more details. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a SetupIntent object. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent the canceled setup intent + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent the confirmed setup intent + */ + public function confirm($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/confirm'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SetupIntent the verified setup intent + */ + public function verifyMicrodeposits($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/verify_microdeposits'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/ShippingRate.php b/libraries/Stripe/lib/ShippingRate.php new file mode 100644 index 00000000000..3724213bce6 --- /dev/null +++ b/libraries/Stripe/lib/ShippingRate.php @@ -0,0 +1,116 @@ +Charge for shipping. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $active Whether the shipping rate can be used for new purchases. Defaults to true. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|\EDD\Vendor\Stripe\StripeObject $delivery_estimate The estimated range for how long shipping will take, meant to be displayable to the customer. This will appear on CheckoutSessions. + * @property null|string $display_name The name of the shipping rate, meant to be displayable to the customer. This will appear on CheckoutSessions. + * @property null|\EDD\Vendor\Stripe\StripeObject $fixed_amount + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $tax_behavior Specifies whether the rate is considered inclusive of taxes or exclusive of taxes. One of inclusive, exclusive, or unspecified. + * @property null|string|\EDD\Vendor\Stripe\TaxCode $tax_code A tax code ID. The Shipping tax code is txcd_92010001. + * @property string $type The type of calculation to use on the shipping rate. + */ +class ShippingRate extends ApiResource +{ + const OBJECT_NAME = 'shipping_rate'; + + use ApiOperations\Update; + + const TAX_BEHAVIOR_EXCLUSIVE = 'exclusive'; + const TAX_BEHAVIOR_INCLUSIVE = 'inclusive'; + const TAX_BEHAVIOR_UNSPECIFIED = 'unspecified'; + + const TYPE_FIXED_AMOUNT = 'fixed_amount'; + + /** + * Creates a new shipping rate object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ShippingRate the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of your shipping rates. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\ShippingRate> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Returns the shipping rate object with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ShippingRate + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates an existing shipping rate object. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\ShippingRate the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Sigma/ScheduledQueryRun.php b/libraries/Stripe/lib/Sigma/ScheduledQueryRun.php new file mode 100644 index 00000000000..ac0b7b239a1 --- /dev/null +++ b/libraries/Stripe/lib/Sigma/ScheduledQueryRun.php @@ -0,0 +1,69 @@ +scheduled a Sigma query, you'll + * receive a sigma.scheduled_query_run.created webhook each time the query + * runs. The webhook contains a ScheduledQueryRun object, which you can use to + * retrieve the query results. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property int $data_load_time When the query was run, Sigma contained a snapshot of your EDD\Vendor\Stripe data at this time. + * @property null|\EDD\Vendor\Stripe\StripeObject $error + * @property null|\EDD\Vendor\Stripe\File $file The file object representing the results of the query. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property int $result_available_until Time at which the result expires and is no longer available for download. + * @property string $sql SQL for the query. + * @property string $status The query's execution status, which will be completed for successful runs, and canceled, failed, or timed_out otherwise. + * @property string $title Title of the query. + */ +class ScheduledQueryRun extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'scheduled_query_run'; + + /** + * Returns a list of scheduled query runs. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Sigma\ScheduledQueryRun> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an scheduled query run. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Sigma\ScheduledQueryRun + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + public static function classUrl() + { + return '/v1/sigma/scheduled_query_runs'; + } +} diff --git a/libraries/Stripe/lib/SingletonApiResource.php b/libraries/Stripe/lib/SingletonApiResource.php new file mode 100644 index 00000000000..9facb6979f6 --- /dev/null +++ b/libraries/Stripe/lib/SingletonApiResource.php @@ -0,0 +1,31 @@ +Source objects allow you to accept a variety of payment methods. They + * represent a customer's payment instrument, and can be used with the EDD\Vendor\Stripe API + * just like a Card object: once chargeable, they can be charged, or can be + * attached to customers. + * + * EDD\Vendor\Stripe doesn't recommend using the deprecated Sources API. + * We recommend that you adopt the PaymentMethods API. + * This newer API provides access to our latest features and payment method types. + * + * Related guides: Sources API and Sources & Customers. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $ach_credit_transfer + * @property null|\EDD\Vendor\Stripe\StripeObject $ach_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $acss_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $alipay + * @property null|int $amount A positive integer in the smallest currency unit (that is, 100 cents for $1.00, or 1 for ¥1, Japanese Yen being a zero-decimal currency) representing the total amount associated with the source. This is the amount for which the source will be chargeable once ready. Required for single_use sources. + * @property null|\EDD\Vendor\Stripe\StripeObject $au_becs_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $bancontact + * @property null|\EDD\Vendor\Stripe\StripeObject $card + * @property null|\EDD\Vendor\Stripe\StripeObject $card_present + * @property string $client_secret The client secret of the source. Used for client-side retrieval using a publishable key. + * @property null|\EDD\Vendor\Stripe\StripeObject $code_verification + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $currency Three-letter ISO code for the currency associated with the source. This is the currency for which the source will be chargeable once ready. Required for single_use sources. + * @property null|string $customer The ID of the customer to which this source is attached. This will not be present when the source has not been attached to a customer. + * @property null|\EDD\Vendor\Stripe\StripeObject $eps + * @property string $flow The authentication flow of the source. flow is one of redirect, receiver, code_verification, none. + * @property null|\EDD\Vendor\Stripe\StripeObject $giropay + * @property null|\EDD\Vendor\Stripe\StripeObject $ideal + * @property null|\EDD\Vendor\Stripe\StripeObject $klarna + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $multibanco + * @property null|\EDD\Vendor\Stripe\StripeObject $owner Information about the owner of the payment instrument that may be used or required by particular source types. + * @property null|\EDD\Vendor\Stripe\StripeObject $p24 + * @property null|\EDD\Vendor\Stripe\StripeObject $receiver + * @property null|\EDD\Vendor\Stripe\StripeObject $redirect + * @property null|\EDD\Vendor\Stripe\StripeObject $sepa_credit_transfer + * @property null|\EDD\Vendor\Stripe\StripeObject $sepa_debit + * @property null|\EDD\Vendor\Stripe\StripeObject $sofort + * @property null|\EDD\Vendor\Stripe\StripeObject $source_order + * @property null|string $statement_descriptor Extra information about a source. This will appear on your customer's statement every time you charge the source. + * @property string $status The status of the source, one of canceled, chargeable, consumed, failed, or pending. Only chargeable sources can be used to create a charge. + * @property null|\EDD\Vendor\Stripe\StripeObject $three_d_secure + * @property string $type The type of the source. The type is a payment method, one of ach_credit_transfer, ach_debit, alipay, bancontact, card, card_present, eps, giropay, ideal, multibanco, klarna, p24, sepa_debit, sofort, three_d_secure, or wechat. An additional hash is included on the source with a name matching this value. It contains additional information specific to the payment method used. + * @property null|string $usage Either reusable or single_use. Whether this source should be reusable or not. Some source types may or may not be reusable by construction, while others may leave the option at creation. If an incompatible value is passed, an error will be returned. + * @property null|\EDD\Vendor\Stripe\StripeObject $wechat + */ +class Source extends ApiResource +{ + const OBJECT_NAME = 'source'; + + use ApiOperations\Update; + + const FLOW_CODE_VERIFICATION = 'code_verification'; + const FLOW_NONE = 'none'; + const FLOW_RECEIVER = 'receiver'; + const FLOW_REDIRECT = 'redirect'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_CHARGEABLE = 'chargeable'; + const STATUS_CONSUMED = 'consumed'; + const STATUS_FAILED = 'failed'; + const STATUS_PENDING = 'pending'; + + const TYPE_ACH_CREDIT_TRANSFER = 'ach_credit_transfer'; + const TYPE_ACH_DEBIT = 'ach_debit'; + const TYPE_ACSS_DEBIT = 'acss_debit'; + const TYPE_ALIPAY = 'alipay'; + const TYPE_AU_BECS_DEBIT = 'au_becs_debit'; + const TYPE_BANCONTACT = 'bancontact'; + const TYPE_CARD = 'card'; + const TYPE_CARD_PRESENT = 'card_present'; + const TYPE_EPS = 'eps'; + const TYPE_GIROPAY = 'giropay'; + const TYPE_IDEAL = 'ideal'; + const TYPE_KLARNA = 'klarna'; + const TYPE_MULTIBANCO = 'multibanco'; + const TYPE_P24 = 'p24'; + const TYPE_SEPA_CREDIT_TRANSFER = 'sepa_credit_transfer'; + const TYPE_SEPA_DEBIT = 'sepa_debit'; + const TYPE_SOFORT = 'sofort'; + const TYPE_THREE_D_SECURE = 'three_d_secure'; + const TYPE_WECHAT = 'wechat'; + + const USAGE_REUSABLE = 'reusable'; + const USAGE_SINGLE_USE = 'single_use'; + + /** + * Creates a new source object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Retrieves an existing source object. Supply the unique source ID from a source + * creation request and EDD\Vendor\Stripe will return the corresponding up-to-date source + * object information. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified source by setting the values of the parameters passed. Any + * parameters not provided will be left unchanged. + * + * This request accepts the metadata and owner as + * arguments. It is also possible to update type specific information for selected + * payment methods. Please refer to our payment method + * guides for more detail. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + use ApiOperations\NestedResource; + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\UnexpectedValueException if the source is not attached to a customer + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source the detached source + */ + public function detach($params = null, $opts = null) + { + self::_validateParams($params); + + $id = $this['id']; + if (!$id) { + $class = static::class; + $msg = "Could not determine which URL to request: {$class} instance " + . "has invalid ID: {$id}"; + + throw new Exception\UnexpectedValueException($msg, null); + } + + if ($this['customer']) { + $base = Customer::classUrl(); + $parentExtn = \urlencode(Util\Util::utf8($this['customer'])); + $extn = \urlencode(Util\Util::utf8($id)); + $url = "{$base}/{$parentExtn}/sources/{$extn}"; + + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + $message = 'This source object does not appear to be currently attached ' + . 'to a customer object.'; + + throw new Exception\UnexpectedValueException($message); + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\SourceTransaction> list of source transactions + */ + public static function allSourceTransactions($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/source_transactions'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Source the verified source + */ + public function verify($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/verify'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/SourceMandateNotification.php b/libraries/Stripe/lib/SourceMandateNotification.php new file mode 100644 index 00000000000..70ee30c1998 --- /dev/null +++ b/libraries/Stripe/lib/SourceMandateNotification.php @@ -0,0 +1,28 @@ +debit_initiated. + * @property null|\EDD\Vendor\Stripe\StripeObject $bacs_debit + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $reason The reason of the mandate notification. Valid reasons are mandate_confirmed or debit_initiated. + * @property null|\EDD\Vendor\Stripe\StripeObject $sepa_debit + * @property \EDD\Vendor\Stripe\Source $source

    Source objects allow you to accept a variety of payment methods. They represent a customer's payment instrument, and can be used with the EDD\Vendor\Stripe API just like a Card object: once chargeable, they can be charged, or can be attached to customers.

    EDD\Vendor\Stripe doesn't recommend using the deprecated Sources API. We recommend that you adopt the PaymentMethods API. This newer API provides access to our latest features and payment method types.

    Related guides: Sources API and Sources & Customers.

    + * @property string $status The status of the mandate notification. Valid statuses are pending or submitted. + * @property string $type The type of source this mandate notification is attached to. Should be the source type identifier code for the payment method, such as three_d_secure. + */ +class SourceMandateNotification extends ApiResource +{ + const OBJECT_NAME = 'source_mandate_notification'; +} diff --git a/libraries/Stripe/lib/SourceTransaction.php b/libraries/Stripe/lib/SourceTransaction.php new file mode 100644 index 00000000000..536c0c01cf5 --- /dev/null +++ b/libraries/Stripe/lib/SourceTransaction.php @@ -0,0 +1,48 @@ +ISO currency code, in lowercase. Must be a supported currency. + * @property null|\EDD\Vendor\Stripe\StripeObject $gbp_credit_transfer + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $paper_check + * @property null|\EDD\Vendor\Stripe\StripeObject $sepa_credit_transfer + * @property string $source The ID of the source this transaction is attached to. + * @property string $status The status of the transaction, one of succeeded, pending, or failed. + * @property string $type The type of source this transaction is attached to. + */ +class SourceTransaction extends ApiResource +{ + const OBJECT_NAME = 'source_transaction'; + + const TYPE_ACH_CREDIT_TRANSFER = 'ach_credit_transfer'; + const TYPE_ACH_DEBIT = 'ach_debit'; + const TYPE_ALIPAY = 'alipay'; + const TYPE_BANCONTACT = 'bancontact'; + const TYPE_CARD = 'card'; + const TYPE_CARD_PRESENT = 'card_present'; + const TYPE_EPS = 'eps'; + const TYPE_GIROPAY = 'giropay'; + const TYPE_IDEAL = 'ideal'; + const TYPE_KLARNA = 'klarna'; + const TYPE_MULTIBANCO = 'multibanco'; + const TYPE_P24 = 'p24'; + const TYPE_SEPA_DEBIT = 'sepa_debit'; + const TYPE_SOFORT = 'sofort'; + const TYPE_THREE_D_SECURE = 'three_d_secure'; + const TYPE_WECHAT = 'wechat'; +} diff --git a/libraries/Stripe/lib/Stripe.php b/libraries/Stripe/lib/Stripe.php new file mode 100644 index 00000000000..d805bbe60b9 --- /dev/null +++ b/libraries/Stripe/lib/Stripe.php @@ -0,0 +1,277 @@ +getService($name); + } + + public function getService($name) + { + if (null === $this->coreServiceFactory) { + $this->coreServiceFactory = new \EDD\Vendor\Stripe\Service\CoreServiceFactory($this); + } + + return $this->coreServiceFactory->getService($name); + } +} diff --git a/libraries/Stripe/lib/StripeClientInterface.php b/libraries/Stripe/lib/StripeClientInterface.php new file mode 100644 index 00000000000..d16ee8b6924 --- /dev/null +++ b/libraries/Stripe/lib/StripeClientInterface.php @@ -0,0 +1,21 @@ + "old_value"] + * + * If we update the object with `metadata[new]=new_value`, the server side + * object now has *both* fields: + * + * metadata = ["old" => "old_value", "new" => "new_value"] + * + * This is okay in itself because usually users will want to treat it as + * additive: + * + * $obj->metadata["new"] = "new_value"; + * $obj->save(); + * + * However, in other cases, they may want to replace the entire existing + * contents: + * + * $obj->metadata = ["new" => "new_value"]; + * $obj->save(); + * + * This is where things get a little bit tricky because in order to clear + * any old keys that may have existed, we actually have to send an explicit + * empty string to the server. So the operation above would have to send + * this form to get the intended behavior: + * + * metadata[old]=&metadata[new]=new_value + * + * This method allows us to track which parameters are considered additive, + * and lets us behave correctly where appropriate when serializing + * parameters to be sent. + * + * @return Util\Set Set of additive parameters + */ + public static function getAdditiveParams() + { + static $additiveParams = null; + if (null === $additiveParams) { + // Set `metadata` as additive so that when it's set directly we remember + // to clear keys that may have been previously set by sending empty + // values for them. + // + // It's possible that not every object has `metadata`, but having this + // option set when there is no `metadata` field is not harmful. + $additiveParams = new Util\Set([ + 'metadata', + ]); + } + + return $additiveParams; + } + + public function __construct($id = null, $opts = null) + { + list($id, $this->_retrieveOptions) = Util\Util::normalizeId($id); + $this->_opts = Util\RequestOptions::parse($opts); + $this->_originalValues = []; + $this->_values = []; + $this->_unsavedValues = new Util\Set(); + $this->_transientValues = new Util\Set(); + if (null !== $id) { + $this->_values['id'] = $id; + } + } + + // Standard accessor magic methods + public function __set($k, $v) + { + if (static::getPermanentAttributes()->includes($k)) { + throw new Exception\InvalidArgumentException( + "Cannot set {$k} on this object. HINT: you can't set: " . + \implode(', ', static::getPermanentAttributes()->toArray()) + ); + } + + if ('' === $v) { + throw new Exception\InvalidArgumentException( + 'You cannot set \'' . $k . '\'to an empty string. ' + . 'We interpret empty strings as NULL in requests. ' + . 'You may set obj->' . $k . ' = NULL to delete the property' + ); + } + + $this->_values[$k] = Util\Util::convertToStripeObject($v, $this->_opts); + $this->dirtyValue($this->_values[$k]); + $this->_unsavedValues->add($k); + } + + /** + * @param mixed $k + * + * @return bool + */ + public function __isset($k) + { + return isset($this->_values[$k]); + } + + public function __unset($k) + { + unset($this->_values[$k]); + $this->_transientValues->add($k); + $this->_unsavedValues->discard($k); + } + + public function &__get($k) + { + // function should return a reference, using $nullval to return a reference to null + $nullval = null; + if (!empty($this->_values) && \array_key_exists($k, $this->_values)) { + return $this->_values[$k]; + } + if (!empty($this->_transientValues) && $this->_transientValues->includes($k)) { + $class = static::class; + $attrs = \implode(', ', \array_keys($this->_values)); + $message = "EDD\Vendor\Stripe Notice: Undefined property of {$class} instance: {$k}. " + . "HINT: The {$k} attribute was set in the past, however. " + . 'It was then wiped when refreshing the object ' + . "with the result returned by Stripe's API, " + . 'probably as a result of a save(). The attributes currently ' + . "available on this object are: {$attrs}"; + Stripe::getLogger()->error($message); + + return $nullval; + } + $class = static::class; + Stripe::getLogger()->error("EDD\Vendor\Stripe Notice: Undefined property of {$class} instance: {$k}"); + + return $nullval; + } + + /** + * Magic method for var_dump output. Only works with PHP >= 5.6. + * + * @return array + */ + public function __debugInfo() + { + return $this->_values; + } + + // ArrayAccess methods + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($k, $v) + { + $this->{$k} = $v; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($k) + { + return \array_key_exists($k, $this->_values); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($k) + { + unset($this->{$k}); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($k) + { + return \array_key_exists($k, $this->_values) ? $this->_values[$k] : null; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->_values); + } + + public function keys() + { + return \array_keys($this->_values); + } + + public function values() + { + return \array_values($this->_values); + } + + /** + * This unfortunately needs to be public to be used in Util\Util. + * + * @param array $values + * @param null|array|string|Util\RequestOptions $opts + * + * @return static the object constructed from the given values + */ + public static function constructFrom($values, $opts = null) + { + $obj = new static(isset($values['id']) ? $values['id'] : null); + $obj->refreshFrom($values, $opts); + + return $obj; + } + + /** + * Refreshes this object using the provided values. + * + * @param array $values + * @param null|array|string|Util\RequestOptions $opts + * @param bool $partial defaults to false + */ + public function refreshFrom($values, $opts, $partial = false) + { + $this->_opts = Util\RequestOptions::parse($opts); + + $this->_originalValues = self::deepCopy($values); + + if ($values instanceof StripeObject) { + $values = $values->toArray(); + } + + // Wipe old state before setting new. This is useful for e.g. updating a + // customer, where there is no persistent card parameter. Mark those values + // which don't persist as transient + if ($partial) { + $removed = new Util\Set(); + } else { + $removed = new Util\Set(\array_diff(\array_keys($this->_values), \array_keys($values))); + } + + foreach ($removed->toArray() as $k) { + unset($this->{$k}); + } + + $this->updateAttributes($values, $opts, false); + foreach ($values as $k => $v) { + $this->_transientValues->discard($k); + $this->_unsavedValues->discard($k); + } + } + + /** + * Mass assigns attributes on the model. + * + * @param array $values + * @param null|array|string|Util\RequestOptions $opts + * @param bool $dirty defaults to true + */ + public function updateAttributes($values, $opts = null, $dirty = true) + { + foreach ($values as $k => $v) { + // Special-case metadata to always be cast as a StripeObject + // This is necessary in case metadata is empty, as PHP arrays do + // not differentiate between lists and hashes, and we consider + // empty arrays to be lists. + if (('metadata' === $k) && (\is_array($v))) { + $this->_values[$k] = StripeObject::constructFrom($v, $opts); + } else { + $this->_values[$k] = Util\Util::convertToStripeObject($v, $opts); + } + if ($dirty) { + $this->dirtyValue($this->_values[$k]); + } + $this->_unsavedValues->add($k); + } + } + + /** + * @param bool $force defaults to false + * + * @return array a recursive mapping of attributes to values for this object, + * including the proper value for deleted attributes + */ + public function serializeParameters($force = false) + { + $updateParams = []; + + foreach ($this->_values as $k => $v) { + // There are a few reasons that we may want to add in a parameter for + // update: + // + // 1. The `$force` option has been set. + // 2. We know that it was modified. + // 3. Its value is a StripeObject. A StripeObject may contain modified + // values within in that its parent StripeObject doesn't know about. + // + $original = \array_key_exists($k, $this->_originalValues) ? $this->_originalValues[$k] : null; + $unsaved = $this->_unsavedValues->includes($k); + if ($force || $unsaved || $v instanceof StripeObject) { + $updateParams[$k] = $this->serializeParamsValue( + $this->_values[$k], + $original, + $unsaved, + $force, + $k + ); + } + } + + // a `null` that makes it out of `serializeParamsValue` signals an empty + // value that we shouldn't appear in the serialized form of the object + return \array_filter( + $updateParams, + function ($v) { + return null !== $v; + } + ); + } + + public function serializeParamsValue($value, $original, $unsaved, $force, $key = null) + { + // The logic here is that essentially any object embedded in another + // object that had a `type` is actually an API resource of a different + // type that's been included in the response. These other resources must + // be updated from their proper endpoints, and therefore they are not + // included when serializing even if they've been modified. + // + // There are _some_ known exceptions though. + // + // For example, if the value is unsaved (meaning the user has set it), and + // it looks like the API resource is persisted with an ID, then we include + // the object so that parameters are serialized with a reference to its + // ID. + // + // Another example is that on save API calls it's sometimes desirable to + // update a customer's default source by setting a new card (or other) + // object with `->source=` and then saving the customer. The + // `saveWithParent` flag to override the default behavior allows us to + // handle these exceptions. + // + // We throw an error if a property was set explicitly but we can't do + // anything with it because the integration is probably not working as the + // user intended it to. + if (null === $value) { + return ''; + } + if (($value instanceof ApiResource) && (!$value->saveWithParent)) { + if (!$unsaved) { + return null; + } + if (isset($value->id)) { + return $value; + } + + throw new Exception\InvalidArgumentException( + "Cannot save property `{$key}` containing an API resource of type " . + \get_class($value) . ". It doesn't appear to be persisted and is " . + 'not marked as `saveWithParent`.' + ); + } + if (\is_array($value)) { + if (Util\Util::isList($value)) { + // Sequential array, i.e. a list + $update = []; + foreach ($value as $v) { + $update[] = $this->serializeParamsValue($v, null, true, $force); + } + // This prevents an array that's unchanged from being resent. + if ($update !== $this->serializeParamsValue($original, null, true, $force, $key)) { + return $update; + } + } else { + // Associative array, i.e. a map + return Util\Util::convertToStripeObject($value, $this->_opts)->serializeParameters(); + } + } elseif ($value instanceof StripeObject) { + $update = $value->serializeParameters($force); + if ($original && $unsaved && $key && static::getAdditiveParams()->includes($key)) { + $update = \array_merge(self::emptyValues($original), $update); + } + + return $update; + } else { + return $value; + } + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Returns an associative array with the key and values composing the + * EDD\Vendor\Stripe object. + * + * @return array the associative array + */ + public function toArray() + { + $maybeToArray = function ($value) { + if (null === $value) { + return null; + } + + return \is_object($value) && \method_exists($value, 'toArray') ? $value->toArray() : $value; + }; + + return \array_reduce(\array_keys($this->_values), function ($acc, $k) use ($maybeToArray) { + if ('_' === \substr((string) $k, 0, 1)) { + return $acc; + } + $v = $this->_values[$k]; + if (Util\Util::isList($v)) { + $acc[$k] = \array_map($maybeToArray, $v); + } else { + $acc[$k] = $maybeToArray($v); + } + + return $acc; + }, []); + } + + /** + * Returns a pretty JSON representation of the EDD\Vendor\Stripe object. + * + * @return string the JSON representation of the EDD\Vendor\Stripe object + */ + public function toJSON() + { + return \json_encode($this->toArray(), \JSON_PRETTY_PRINT); + } + + public function __toString() + { + $class = static::class; + + return $class . ' JSON: ' . $this->toJSON(); + } + + /** + * Sets all keys within the StripeObject as unsaved so that they will be + * included with an update when `serializeParameters` is called. This + * method is also recursive, so any StripeObjects contained as values or + * which are values in a tenant array are also marked as dirty. + */ + public function dirty() + { + $this->_unsavedValues = new Util\Set(\array_keys($this->_values)); + foreach ($this->_values as $k => $v) { + $this->dirtyValue($v); + } + } + + protected function dirtyValue($value) + { + if (\is_array($value)) { + foreach ($value as $v) { + $this->dirtyValue($v); + } + } elseif ($value instanceof StripeObject) { + $value->dirty(); + } + } + + /** + * Produces a deep copy of the given object including support for arrays + * and StripeObjects. + * + * @param mixed $obj + */ + protected static function deepCopy($obj) + { + if (\is_array($obj)) { + $copy = []; + foreach ($obj as $k => $v) { + $copy[$k] = self::deepCopy($v); + } + + return $copy; + } + if ($obj instanceof StripeObject) { + return $obj::constructFrom( + self::deepCopy($obj->_values), + clone $obj->_opts + ); + } + + return $obj; + } + + /** + * Returns a hash of empty values for all the values that are in the given + * StripeObject. + * + * @param mixed $obj + */ + public static function emptyValues($obj) + { + if (\is_array($obj)) { + $values = $obj; + } elseif ($obj instanceof StripeObject) { + $values = $obj->_values; + } else { + throw new Exception\InvalidArgumentException( + 'empty_values got unexpected object type: ' . \get_class($obj) + ); + } + + return \array_fill_keys(\array_keys($values), ''); + } + + /** + * @return null|ApiResponse The last response from the EDD\Vendor\Stripe API + */ + public function getLastResponse() + { + return $this->_lastResponse; + } + + /** + * Sets the last response from the EDD\Vendor\Stripe API. + * + * @param ApiResponse $resp + */ + public function setLastResponse($resp) + { + $this->_lastResponse = $resp; + } + + /** + * Indicates whether or not the resource has been deleted on the server. + * Note that some, but not all, resources can indicate whether they have + * been deleted. + * + * @return bool whether the resource is deleted + */ + public function isDeleted() + { + return isset($this->_values['deleted']) ? $this->_values['deleted'] : false; + } +} diff --git a/libraries/Stripe/lib/StripeStreamingClientInterface.php b/libraries/Stripe/lib/StripeStreamingClientInterface.php new file mode 100644 index 00000000000..4467367b964 --- /dev/null +++ b/libraries/Stripe/lib/StripeStreamingClientInterface.php @@ -0,0 +1,11 @@ +Creating subscriptions + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string|\EDD\Vendor\Stripe\Application $application ID of the Connect Application that created the subscription. + * @property null|float $application_fee_percent A non-negative decimal between 0 and 100, with at most two decimal places. This represents the percentage of the subscription invoice total that will be transferred to the application owner's EDD\Vendor\Stripe account. + * @property \EDD\Vendor\Stripe\StripeObject $automatic_tax + * @property int $billing_cycle_anchor The reference point that aligns future billing cycle dates. It sets the day of week for week intervals, the day of month for month and year intervals, and the month of year for year intervals. The timestamp is in UTC format. + * @property null|\EDD\Vendor\Stripe\StripeObject $billing_cycle_anchor_config The fixed values used to calculate the billing_cycle_anchor. + * @property null|\EDD\Vendor\Stripe\StripeObject $billing_thresholds Define thresholds at which an invoice will be sent, and the subscription advanced to a new billing period + * @property null|int $cancel_at A date in the future at which the subscription will automatically get canceled + * @property bool $cancel_at_period_end Whether this subscription will (if status=active) or did (if status=canceled) cancel at the end of the current billing period. + * @property null|int $canceled_at If the subscription has been canceled, the date of that cancellation. If the subscription was canceled with cancel_at_period_end, canceled_at will reflect the time of the most recent update request, not the end of the subscription period when the subscription is automatically moved to a canceled state. + * @property null|\EDD\Vendor\Stripe\StripeObject $cancellation_details Details about why this subscription was cancelled + * @property string $collection_method Either charge_automatically, or send_invoice. When charging automatically, EDD\Vendor\Stripe will attempt to pay this subscription at the end of the cycle using the default source attached to the customer. When sending an invoice, EDD\Vendor\Stripe will email your customer an invoice with payment instructions and mark the subscription as active. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property int $current_period_end End of the current period that the subscription has been invoiced for. At the end of this period, a new invoice will be created. + * @property int $current_period_start Start of the current period that the subscription has been invoiced for. + * @property string|\EDD\Vendor\Stripe\Customer $customer ID of the customer who owns the subscription. + * @property null|int $days_until_due Number of days a customer has to pay invoices generated by this subscription. This value will be null for subscriptions where collection_method=charge_automatically. + * @property null|string|\EDD\Vendor\Stripe\PaymentMethod $default_payment_method ID of the default payment method for the subscription. It must belong to the customer associated with the subscription. This takes precedence over default_source. If neither are set, invoices will use the customer's invoice_settings.default_payment_method or default_source. + * @property null|string|\EDD\Vendor\Stripe\Account|\EDD\Vendor\Stripe\BankAccount|\EDD\Vendor\Stripe\Card|\EDD\Vendor\Stripe\Source $default_source ID of the default payment source for the subscription. It must belong to the customer associated with the subscription and be in a chargeable state. If default_payment_method is also set, default_payment_method will take precedence. If neither are set, invoices will use the customer's invoice_settings.default_payment_method or default_source. + * @property null|\EDD\Vendor\Stripe\TaxRate[] $default_tax_rates The tax rates that will apply to any subscription item that does not have tax_rates set. Invoices created will have their default_tax_rates populated from the subscription. + * @property null|string $description The subscription's description, meant to be displayable to the customer. Use this field to optionally store an explanation of the subscription for rendering in EDD\Vendor\Stripe surfaces and certain local payment methods UIs. + * @property null|\EDD\Vendor\Stripe\Discount $discount Describes the current discount applied to this subscription, if there is one. When billing, a discount applied to a subscription overrides a discount applied on a customer-wide basis. This field has been deprecated and will be removed in a future API version. Use discounts instead. + * @property (string|\EDD\Vendor\Stripe\Discount)[] $discounts The discounts applied to the subscription. Subscription item discounts are applied before subscription discounts. Use expand[]=discounts to expand each discount. + * @property null|int $ended_at If the subscription has ended, the date the subscription ended. + * @property \EDD\Vendor\Stripe\StripeObject $invoice_settings + * @property \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\SubscriptionItem> $items List of subscription items, each with an attached price. + * @property null|string|\EDD\Vendor\Stripe\Invoice $latest_invoice The most recent invoice this subscription has generated. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|int $next_pending_invoice_item_invoice Specifies the approximate timestamp on which any pending invoice items will be billed according to the schedule provided at pending_invoice_item_interval. + * @property null|string|\EDD\Vendor\Stripe\Account $on_behalf_of The account (if any) the charge was made on behalf of for charges associated with this subscription. See the Connect documentation for details. + * @property null|\EDD\Vendor\Stripe\StripeObject $pause_collection If specified, payment collection for this subscription will be paused. Note that the subscription status will be unchanged and will not be updated to paused. Learn more about pausing collection. + * @property null|\EDD\Vendor\Stripe\StripeObject $payment_settings Payment settings passed on to invoices created by the subscription. + * @property null|\EDD\Vendor\Stripe\StripeObject $pending_invoice_item_interval Specifies an interval for how often to bill for any pending invoice items. It is analogous to calling Create an invoice for the given subscription at the specified interval. + * @property null|string|\EDD\Vendor\Stripe\SetupIntent $pending_setup_intent You can use this SetupIntent to collect user authentication when creating a subscription without immediate payment or updating a subscription's payment method, allowing you to optimize for off-session payments. Learn more in the SCA Migration Guide. + * @property null|\EDD\Vendor\Stripe\StripeObject $pending_update If specified, pending updates that will be applied to the subscription once the latest_invoice has been paid. + * @property null|string|\EDD\Vendor\Stripe\SubscriptionSchedule $schedule The schedule attached to the subscription + * @property int $start_date Date when the subscription was first created. The date might differ from the created date due to backdating. + * @property string $status

    Possible values are incomplete, incomplete_expired, trialing, active, past_due, canceled, unpaid, or paused.

    For collection_method=charge_automatically a subscription moves into incomplete if the initial payment attempt fails. A subscription in this status can only have metadata and default_source updated. Once the first invoice is paid, the subscription moves into an active status. If the first invoice is not paid within 23 hours, the subscription transitions to incomplete_expired. This is a terminal status, the open invoice will be voided and no further invoices will be generated.

    A subscription that is currently in a trial period is trialing and moves to active when the trial period is over.

    A subscription can only enter a paused status when a trial ends without a payment method. A paused subscription doesn't generate invoices and can be resumed after your customer adds their payment method. The paused status is different from pausing collection, which still generates invoices and leaves the subscription's status unchanged.

    If subscription collection_method=charge_automatically, it becomes past_due when payment is required but cannot be paid (due to failed payment or awaiting additional user actions). Once EDD\Vendor\Stripe has exhausted all payment retry attempts, the subscription will become canceled or unpaid (depending on your subscriptions settings).

    If subscription collection_method=send_invoice it becomes past_due when its invoice is not paid by the due date, and canceled or unpaid if it is still not paid by an additional deadline after that. Note that when a subscription has a status of unpaid, no subsequent invoices will be attempted (invoices will be created, but then immediately automatically closed). After receiving updated payment information from a customer, you may choose to reopen and pay their closed invoices.

    + * @property null|string|\EDD\Vendor\Stripe\TestHelpers\TestClock $test_clock ID of the test clock this subscription belongs to. + * @property null|\EDD\Vendor\Stripe\StripeObject $transfer_data The account (if any) the subscription's payments will be attributed to for tax reporting, and where funds from each payment will be transferred to for each of the subscription's invoices. + * @property null|int $trial_end If the subscription has a trial, the end of that trial. + * @property null|\EDD\Vendor\Stripe\StripeObject $trial_settings Settings related to subscription trials. + * @property null|int $trial_start If the subscription has a trial, the beginning of that trial. + */ +class Subscription extends ApiResource +{ + const OBJECT_NAME = 'subscription'; + + use ApiOperations\Update; + + const COLLECTION_METHOD_CHARGE_AUTOMATICALLY = 'charge_automatically'; + const COLLECTION_METHOD_SEND_INVOICE = 'send_invoice'; + + const STATUS_ACTIVE = 'active'; + const STATUS_CANCELED = 'canceled'; + const STATUS_INCOMPLETE = 'incomplete'; + const STATUS_INCOMPLETE_EXPIRED = 'incomplete_expired'; + const STATUS_PAST_DUE = 'past_due'; + const STATUS_PAUSED = 'paused'; + const STATUS_TRIALING = 'trialing'; + const STATUS_UNPAID = 'unpaid'; + + /** + * Creates a new subscription on an existing customer. Each customer can have up to + * 500 active or scheduled subscriptions. + * + * When you create a subscription with + * collection_method=charge_automatically, the first invoice is + * finalized as part of the request. The payment_behavior parameter + * determines the exact behavior of the initial payment. + * + * To start subscriptions where the first invoice always begins in a + * draft status, use subscription + * schedules instead. Schedules provide the flexibility to model more complex + * billing configurations that change over time. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * By default, returns a list of subscriptions that have not been canceled. In + * order to list canceled subscriptions, specify status=canceled. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Subscription> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the subscription with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates an existing subscription to match the specified parameters. When + * changing prices or quantities, we optionally prorate the price we charge next + * month to make up for any price changes. To preview how the proration is + * calculated, use the create + * preview endpoint. + * + * By default, we prorate subscription changes. For example, if a customer signs up + * on May 1 for a 100 price, they’ll be billed + * 100 immediately. If on May 15 they switch to a + * 200 price, then on June 1 they’ll be billed + * 250 (200 for a renewal of her + * subscription, plus a 50 prorating adjustment for half of + * the previous month’s 100 difference). Similarly, a + * downgrade generates a credit that is applied to the next invoice. We also + * prorate when you make quantity changes. + * + * Switching prices does not normally change the billing date or generate an + * immediate charge unless: + * + *
    • The billing interval is changed (for example, from monthly to + * yearly).
    • The subscription moves from free to paid.
    • A trial + * starts or ends.
    + * + * In these cases, we apply a credit for the unused time on the previous price, + * immediately charge the customer using the new price, and reset the billing date. + * Learn about how EDD\Vendor\Stripe + * immediately attempts payment for subscription changes. + * + * If you want to charge for an upgrade immediately, pass + * proration_behavior as always_invoice to create + * prorations, automatically invoice the customer for those proration adjustments, + * and attempt to collect payment. If you pass create_prorations, the + * prorations are created but not automatically invoiced. If you want to bill the + * customer for the prorations before the subscription’s renewal date, you need to + * manually invoice the customer. + * + * If you don’t want to prorate, set the proration_behavior option to + * none. With this option, the customer is billed + * 100 on May 1 and 200 on June 1. + * Similarly, if you set proration_behavior to none when + * switching between different billing intervals (for example, from monthly to + * yearly), we don’t generate any credits for the old subscription’s unused time. + * We still reset the billing date and bill immediately for the new subscription. + * + * Updating the quantity on a subscription many times in an hour may result in rate limiting. If you need to bill for a frequently + * changing quantity, consider integrating usage-based billing instead. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + use ApiOperations\Delete { + delete as protected _delete; + } + + public static function getSavedNestedResources() + { + static $savedNestedResources = null; + if (null === $savedNestedResources) { + $savedNestedResources = new Util\Set([ + 'source', + ]); + } + + return $savedNestedResources; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription the updated subscription + */ + public function deleteDiscount($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/discount'; + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom(['discount' => null], $opts, true); + + return $this; + } + + const PAYMENT_BEHAVIOR_ALLOW_INCOMPLETE = 'allow_incomplete'; + const PAYMENT_BEHAVIOR_DEFAULT_INCOMPLETE = 'default_incomplete'; + const PAYMENT_BEHAVIOR_ERROR_IF_INCOMPLETE = 'error_if_incomplete'; + const PAYMENT_BEHAVIOR_PENDING_IF_INCOMPLETE = 'pending_if_incomplete'; + + const PRORATION_BEHAVIOR_ALWAYS_INVOICE = 'always_invoice'; + const PRORATION_BEHAVIOR_CREATE_PRORATIONS = 'create_prorations'; + const PRORATION_BEHAVIOR_NONE = 'none'; + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription the canceled subscription + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Subscription the resumed subscription + */ + public function resume($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/resume'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SearchResult<\EDD\Vendor\Stripe\Subscription> the subscription search results + */ + public static function search($params = null, $opts = null) + { + $url = '/v1/subscriptions/search'; + + return static::_requestPage($url, \EDD\Vendor\Stripe\SearchResult::class, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/SubscriptionItem.php b/libraries/Stripe/lib/SubscriptionItem.php new file mode 100644 index 00000000000..29e6d243366 --- /dev/null +++ b/libraries/Stripe/lib/SubscriptionItem.php @@ -0,0 +1,164 @@ +expand[]=discounts to expand each discount. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property \EDD\Vendor\Stripe\Plan $plan

    You can now model subscriptions more flexibly using the Prices API. It replaces the Plans API and is backwards compatible to simplify your migration.

    Plans define the base price, currency, and billing cycle for recurring purchases of products. Products help you track inventory or provisioning, and plans help you track pricing. Different physical goods or levels of service should be represented by products, and pricing options should be represented by plans. This approach lets you change prices without having to change your provisioning scheme.

    For example, you might have a single "gold" product that has plans for $10/month, $100/year, €9/month, and €90/year.

    Related guides: Set up a subscription and more about products and prices.

    + * @property \EDD\Vendor\Stripe\Price $price

    Prices define the unit cost, currency, and (optional) billing cycle for both recurring and one-time purchases of products. Products help you track inventory or provisioning, and prices help you track payment terms. Different physical goods or levels of service should be represented by products, and pricing options should be represented by prices. This approach lets you change prices without having to change your provisioning scheme.

    For example, you might have a single "gold" product that has prices for $10/month, $100/year, and €9 once.

    Related guides: Set up a subscription, create an invoice, and more about products and prices.

    + * @property null|int $quantity The quantity of the plan to which the customer should be subscribed. + * @property string $subscription The subscription this subscription_item belongs to. + * @property null|\EDD\Vendor\Stripe\TaxRate[] $tax_rates The tax rates which apply to this subscription_item. When set, the default_tax_rates on the subscription do not apply to this subscription_item. + */ +class SubscriptionItem extends ApiResource +{ + const OBJECT_NAME = 'subscription_item'; + + use ApiOperations\NestedResource; + use ApiOperations\Update; + + /** + * Adds a new item to an existing subscription. No existing items will be changed + * or replaced. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionItem the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes an item from the subscription. Removing a subscription item from a + * subscription will not cancel the subscription. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionItem the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of your subscription items for a given subscription. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\SubscriptionItem> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the subscription item with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionItem + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the plan or quantity of an item on a current subscription. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionItem the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + const PATH_USAGE_RECORDS = '/usage_records'; + + /** + * @param string $id the ID of the subscription item on which to create the usage record + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\UsageRecord + */ + public static function createUsageRecord($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_USAGE_RECORDS, $params, $opts); + } + const PATH_USAGE_RECORD_SUMMARIES = '/usage_record_summaries'; + + /** + * @param string $id the ID of the subscription item on which to retrieve the usage record summaries + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\UsageRecordSummary> the list of usage record summaries + */ + public static function allUsageRecordSummaries($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_USAGE_RECORD_SUMMARIES, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/SubscriptionSchedule.php b/libraries/Stripe/lib/SubscriptionSchedule.php new file mode 100644 index 00000000000..aa4793334b5 --- /dev/null +++ b/libraries/Stripe/lib/SubscriptionSchedule.php @@ -0,0 +1,165 @@ +Subscription schedules + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string|\EDD\Vendor\Stripe\Application $application ID of the Connect Application that created the schedule. + * @property null|int $canceled_at Time at which the subscription schedule was canceled. Measured in seconds since the Unix epoch. + * @property null|int $completed_at Time at which the subscription schedule was completed. Measured in seconds since the Unix epoch. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|\EDD\Vendor\Stripe\StripeObject $current_phase Object representing the start and end dates for the current phase of the subscription schedule, if it is active. + * @property string|\EDD\Vendor\Stripe\Customer $customer ID of the customer who owns the subscription schedule. + * @property \EDD\Vendor\Stripe\StripeObject $default_settings + * @property string $end_behavior Behavior of the subscription schedule and underlying subscription when it ends. Possible values are release or cancel with the default being release. release will end the subscription schedule and keep the underlying subscription running. cancel will end the subscription schedule and cancel the underlying subscription. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property \EDD\Vendor\Stripe\StripeObject[] $phases Configuration for the subscription schedule's phases. + * @property null|int $released_at Time at which the subscription schedule was released. Measured in seconds since the Unix epoch. + * @property null|string $released_subscription ID of the subscription once managed by the subscription schedule (if it is released). + * @property string $status The present status of the subscription schedule. Possible values are not_started, active, completed, released, and canceled. You can read more about the different states in our behavior guide. + * @property null|string|\EDD\Vendor\Stripe\Subscription $subscription ID of the subscription managed by the subscription schedule. + * @property null|string|\EDD\Vendor\Stripe\TestHelpers\TestClock $test_clock ID of the test clock this subscription schedule belongs to. + */ +class SubscriptionSchedule extends ApiResource +{ + const OBJECT_NAME = 'subscription_schedule'; + + use ApiOperations\Update; + + const END_BEHAVIOR_CANCEL = 'cancel'; + const END_BEHAVIOR_NONE = 'none'; + const END_BEHAVIOR_RELEASE = 'release'; + const END_BEHAVIOR_RENEW = 'renew'; + + const STATUS_ACTIVE = 'active'; + const STATUS_CANCELED = 'canceled'; + const STATUS_COMPLETED = 'completed'; + const STATUS_NOT_STARTED = 'not_started'; + const STATUS_RELEASED = 'released'; + + /** + * Creates a new subscription schedule object. Each customer can have up to 500 + * active or scheduled subscriptions. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Retrieves the list of your subscription schedules. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\SubscriptionSchedule> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing subscription schedule. You only need to + * supply the unique subscription schedule identifier that was returned upon + * subscription schedule creation. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates an existing subscription schedule. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule the canceled subscription schedule + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\SubscriptionSchedule the released subscription schedule + */ + public function release($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/release'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Tax/Calculation.php b/libraries/Stripe/lib/Tax/Calculation.php new file mode 100644 index 00000000000..fc85a9a05bb --- /dev/null +++ b/libraries/Stripe/lib/Tax/Calculation.php @@ -0,0 +1,93 @@ +Calculate tax in your custom payment flow + * + * @property null|string $id Unique identifier for the calculation. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount_total Total amount after taxes in the smallest currency unit. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $customer The ID of an existing Customer used for the resource. + * @property \EDD\Vendor\Stripe\StripeObject $customer_details + * @property null|int $expires_at Timestamp of date at which the tax calculation will expire. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Tax\CalculationLineItem> $line_items The list of items the customer is purchasing. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $ship_from_details The details of the ship from location, such as the address. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_cost The shipping cost details for the calculation. + * @property int $tax_amount_exclusive The amount of tax to be collected on top of the line item prices. + * @property int $tax_amount_inclusive The amount of tax already included in the line item prices. + * @property \EDD\Vendor\Stripe\StripeObject[] $tax_breakdown Breakdown of individual tax amounts that add up to the total. + * @property int $tax_date Timestamp of date at which the tax rules and rates in effect applies for the calculation. + */ +class Calculation extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'tax.calculation'; + + /** + * Calculates tax based on the input and returns a Tax Calculation + * object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Calculation the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Retrieves a Tax Calculation object, if the calculation hasn’t + * expired. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Calculation + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Tax\CalculationLineItem> list of calculation line items + */ + public static function allLineItems($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/line_items'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Tax/CalculationLineItem.php b/libraries/Stripe/lib/Tax/CalculationLineItem.php new file mode 100644 index 00000000000..0666a0297b8 --- /dev/null +++ b/libraries/Stripe/lib/Tax/CalculationLineItem.php @@ -0,0 +1,26 @@ +smallest currency unit. If tax_behavior=inclusive, then this amount includes taxes. Otherwise, taxes were calculated on top of this amount. + * @property int $amount_tax The amount of tax calculated for this line item, in the smallest currency unit. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $product The ID of an existing Product. + * @property int $quantity The number of units of the item being purchased. For reversals, this is the quantity reversed. + * @property null|string $reference A custom identifier for this line item. + * @property string $tax_behavior Specifies whether the amount includes taxes. If tax_behavior=inclusive, then the amount includes taxes. + * @property null|\EDD\Vendor\Stripe\StripeObject[] $tax_breakdown Detailed account of taxes relevant to this line item. + * @property string $tax_code The tax code ID used for this resource. + */ +class CalculationLineItem extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'tax.calculation_line_item'; + + const TAX_BEHAVIOR_EXCLUSIVE = 'exclusive'; + const TAX_BEHAVIOR_INCLUSIVE = 'inclusive'; +} diff --git a/libraries/Stripe/lib/Tax/Registration.php b/libraries/Stripe/lib/Tax/Registration.php new file mode 100644 index 00000000000..d6e7fd8ccb7 --- /dev/null +++ b/libraries/Stripe/lib/Tax/Registration.php @@ -0,0 +1,117 @@ +Registration lets us know that your business is registered to collect tax on payments within a region, enabling you to automatically collect tax. + * + * EDD\Vendor\Stripe doesn't register on your behalf with the relevant authorities when you create a Tax Registration object. For more information on how to register to collect tax, see our guide. + * + * Related guide: Using the Registrations API + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $active_from Time at which the registration becomes active. Measured in seconds since the Unix epoch. + * @property string $country Two-letter country code (ISO 3166-1 alpha-2). + * @property \EDD\Vendor\Stripe\StripeObject $country_options + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|int $expires_at If set, the registration stops being active at this time. If not set, the registration will be active indefinitely. Measured in seconds since the Unix epoch. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $status The status of the registration. This field is present for convenience and can be deduced from active_from and expires_at. + */ +class Registration extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'tax.registration'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const STATUS_ACTIVE = 'active'; + const STATUS_EXPIRED = 'expired'; + const STATUS_SCHEDULED = 'scheduled'; + + /** + * Creates a new Tax Registration object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Registration the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of Tax Registration objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Tax\Registration> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Returns a Tax Registration object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Registration + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates an existing Tax Registration object. + * + * A registration cannot be deleted after it has been created. If you wish to end a + * registration you may do so by setting expires_at. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Registration the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Tax/Settings.php b/libraries/Stripe/lib/Tax/Settings.php new file mode 100644 index 00000000000..d820cd96ff8 --- /dev/null +++ b/libraries/Stripe/lib/Tax/Settings.php @@ -0,0 +1,86 @@ +Settings to manage configurations used by EDD\Vendor\Stripe Tax calculations. + * + * Related guide: Using the Settings API + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property \EDD\Vendor\Stripe\StripeObject $defaults + * @property null|\EDD\Vendor\Stripe\StripeObject $head_office The place where your business is located. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $status The active status indicates you have all required settings to calculate tax. A status can transition out of active when new required settings are introduced. + * @property \EDD\Vendor\Stripe\StripeObject $status_details + */ +class Settings extends \EDD\Vendor\Stripe\SingletonApiResource +{ + const OBJECT_NAME = 'tax.settings'; + + const STATUS_ACTIVE = 'active'; + const STATUS_PENDING = 'pending'; + + /** + * Retrieves Tax Settings for a merchant. + * + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Settings + */ + public static function retrieve($opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static(null, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return static the updated resource + */ + public static function update($params = null, $opts = null) + { + self::_validateParams($params); + $url = '/v1/tax/settings'; + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return static the saved resource + * + * @deprecated The `save` method is deprecated and will be removed in a + * future major version of the library. Use the static method `update` + * on the resource instead. + */ + public function save($opts = null) + { + $params = $this->serializeParameters(); + if (\count($params) > 0) { + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('post', $url, $params, $opts, ['save']); + $this->refreshFrom($response, $opts); + } + + return $this; + } +} diff --git a/libraries/Stripe/lib/Tax/Transaction.php b/libraries/Stripe/lib/Tax/Transaction.php new file mode 100644 index 00000000000..0ed28d3e292 --- /dev/null +++ b/libraries/Stripe/lib/Tax/Transaction.php @@ -0,0 +1,109 @@ +Calculate tax in your custom payment flow + * + * @property string $id Unique identifier for the transaction. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $customer The ID of an existing Customer used for the resource. + * @property \EDD\Vendor\Stripe\StripeObject $customer_details + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Tax\TransactionLineItem> $line_items The tax collected or refunded, by line item. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property int $posted_at The Unix timestamp representing when the tax liability is assumed or reduced. + * @property string $reference A custom unique identifier, such as 'myOrder_123'. + * @property null|\EDD\Vendor\Stripe\StripeObject $reversal If type=reversal, contains information about what was reversed. + * @property null|\EDD\Vendor\Stripe\StripeObject $ship_from_details The details of the ship from location, such as the address. + * @property null|\EDD\Vendor\Stripe\StripeObject $shipping_cost The shipping cost details for the transaction. + * @property int $tax_date Timestamp of date at which the tax rules and rates in effect applies for the calculation. + * @property string $type If reversal, this transaction reverses an earlier transaction. + */ +class Transaction extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'tax.transaction'; + + const TYPE_REVERSAL = 'reversal'; + const TYPE_TRANSACTION = 'transaction'; + + /** + * Retrieves a Tax Transaction object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Transaction + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Transaction the created transaction + */ + public static function createFromCalculation($params = null, $opts = null) + { + $url = static::classUrl() . '/create_from_calculation'; + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Tax\Transaction the created transaction + */ + public static function createReversal($params = null, $opts = null) + { + $url = static::classUrl() . '/create_reversal'; + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param string $id + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Tax\TransactionLineItem> list of transaction line items + */ + public static function allLineItems($id, $params = null, $opts = null) + { + $url = static::resourceUrl($id) . '/line_items'; + list($response, $opts) = static::_staticRequest('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Tax/TransactionLineItem.php b/libraries/Stripe/lib/Tax/TransactionLineItem.php new file mode 100644 index 00000000000..db5acf9c390 --- /dev/null +++ b/libraries/Stripe/lib/Tax/TransactionLineItem.php @@ -0,0 +1,31 @@ +smallest currency unit. If tax_behavior=inclusive, then this amount includes taxes. Otherwise, taxes were calculated on top of this amount. + * @property int $amount_tax The amount of tax calculated for this line item, in the smallest currency unit. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $product The ID of an existing Product. + * @property int $quantity The number of units of the item being purchased. For reversals, this is the quantity reversed. + * @property string $reference A custom identifier for this line item in the transaction. + * @property null|\EDD\Vendor\Stripe\StripeObject $reversal If type=reversal, contains information about what was reversed. + * @property string $tax_behavior Specifies whether the amount includes taxes. If tax_behavior=inclusive, then the amount includes taxes. + * @property string $tax_code The tax code ID used for this resource. + * @property string $type If reversal, this line item reverses an earlier transaction. + */ +class TransactionLineItem extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'tax.transaction_line_item'; + + const TAX_BEHAVIOR_EXCLUSIVE = 'exclusive'; + const TAX_BEHAVIOR_INCLUSIVE = 'inclusive'; + + const TYPE_REVERSAL = 'reversal'; + const TYPE_TRANSACTION = 'transaction'; +} diff --git a/libraries/Stripe/lib/TaxCode.php b/libraries/Stripe/lib/TaxCode.php new file mode 100644 index 00000000000..76dbd05bc85 --- /dev/null +++ b/libraries/Stripe/lib/TaxCode.php @@ -0,0 +1,56 @@ +Tax codes classify goods and services for tax purposes. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property string $description A detailed description of which types of products the tax code represents. + * @property string $name A short name for the tax code. + */ +class TaxCode extends ApiResource +{ + const OBJECT_NAME = 'tax_code'; + + /** + * A list of all tax codes + * available to add to Products in order to allow specific tax calculations. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TaxCode> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing tax code. Supply the unique tax code ID and + * EDD\Vendor\Stripe will return the corresponding tax code information. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxCode + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/TaxDeductedAtSource.php b/libraries/Stripe/lib/TaxDeductedAtSource.php new file mode 100644 index 00000000000..8cfaed797a7 --- /dev/null +++ b/libraries/Stripe/lib/TaxDeductedAtSource.php @@ -0,0 +1,17 @@ +customer or account. + * Customer and account tax IDs get displayed on related invoices and credit notes. + * + * Related guides: Customer tax identification numbers, Account tax IDs + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $country Two-letter ISO code representing the country of the tax ID. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string|\EDD\Vendor\Stripe\Customer $customer ID of the customer. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $owner The account or customer the tax ID belongs to. + * @property string $type Type of the tax ID, one of ad_nrt, ae_trn, ar_cuit, au_abn, au_arn, bg_uic, bh_vat, bo_tin, br_cnpj, br_cpf, ca_bn, ca_gst_hst, ca_pst_bc, ca_pst_mb, ca_pst_sk, ca_qst, ch_uid, ch_vat, cl_tin, cn_tin, co_nit, cr_tin, de_stn, do_rcn, ec_ruc, eg_tin, es_cif, eu_oss_vat, eu_vat, gb_vat, ge_vat, hk_br, hr_oib, hu_tin, id_npwp, il_vat, in_gst, is_vat, jp_cn, jp_rn, jp_trn, ke_pin, kr_brn, kz_bin, li_uid, mx_rfc, my_frp, my_itn, my_sst, ng_tin, no_vat, no_voec, nz_gst, om_vat, pe_ruc, ph_tin, ro_tin, rs_pib, ru_inn, ru_kpp, sa_vat, sg_gst, sg_uen, si_tin, sv_nit, th_vat, tr_tin, tw_vat, ua_vat, us_ein, uy_ruc, ve_rif, vn_tin, or za_vat. Note that some legacy tax IDs have type unknown + * @property string $value Value of the tax ID. + * @property null|\EDD\Vendor\Stripe\StripeObject $verification Tax ID verification information. + */ +class TaxId extends ApiResource +{ + const OBJECT_NAME = 'tax_id'; + + const TYPE_AD_NRT = 'ad_nrt'; + const TYPE_AE_TRN = 'ae_trn'; + const TYPE_AR_CUIT = 'ar_cuit'; + const TYPE_AU_ABN = 'au_abn'; + const TYPE_AU_ARN = 'au_arn'; + const TYPE_BG_UIC = 'bg_uic'; + const TYPE_BH_VAT = 'bh_vat'; + const TYPE_BO_TIN = 'bo_tin'; + const TYPE_BR_CNPJ = 'br_cnpj'; + const TYPE_BR_CPF = 'br_cpf'; + const TYPE_CA_BN = 'ca_bn'; + const TYPE_CA_GST_HST = 'ca_gst_hst'; + const TYPE_CA_PST_BC = 'ca_pst_bc'; + const TYPE_CA_PST_MB = 'ca_pst_mb'; + const TYPE_CA_PST_SK = 'ca_pst_sk'; + const TYPE_CA_QST = 'ca_qst'; + const TYPE_CH_UID = 'ch_uid'; + const TYPE_CH_VAT = 'ch_vat'; + const TYPE_CL_TIN = 'cl_tin'; + const TYPE_CN_TIN = 'cn_tin'; + const TYPE_CO_NIT = 'co_nit'; + const TYPE_CR_TIN = 'cr_tin'; + const TYPE_DE_STN = 'de_stn'; + const TYPE_DO_RCN = 'do_rcn'; + const TYPE_EC_RUC = 'ec_ruc'; + const TYPE_EG_TIN = 'eg_tin'; + const TYPE_ES_CIF = 'es_cif'; + const TYPE_EU_OSS_VAT = 'eu_oss_vat'; + const TYPE_EU_VAT = 'eu_vat'; + const TYPE_GB_VAT = 'gb_vat'; + const TYPE_GE_VAT = 'ge_vat'; + const TYPE_HK_BR = 'hk_br'; + const TYPE_HR_OIB = 'hr_oib'; + const TYPE_HU_TIN = 'hu_tin'; + const TYPE_ID_NPWP = 'id_npwp'; + const TYPE_IL_VAT = 'il_vat'; + const TYPE_IN_GST = 'in_gst'; + const TYPE_IS_VAT = 'is_vat'; + const TYPE_JP_CN = 'jp_cn'; + const TYPE_JP_RN = 'jp_rn'; + const TYPE_JP_TRN = 'jp_trn'; + const TYPE_KE_PIN = 'ke_pin'; + const TYPE_KR_BRN = 'kr_brn'; + const TYPE_KZ_BIN = 'kz_bin'; + const TYPE_LI_UID = 'li_uid'; + const TYPE_MX_RFC = 'mx_rfc'; + const TYPE_MY_FRP = 'my_frp'; + const TYPE_MY_ITN = 'my_itn'; + const TYPE_MY_SST = 'my_sst'; + const TYPE_NG_TIN = 'ng_tin'; + const TYPE_NO_VAT = 'no_vat'; + const TYPE_NO_VOEC = 'no_voec'; + const TYPE_NZ_GST = 'nz_gst'; + const TYPE_OM_VAT = 'om_vat'; + const TYPE_PE_RUC = 'pe_ruc'; + const TYPE_PH_TIN = 'ph_tin'; + const TYPE_RO_TIN = 'ro_tin'; + const TYPE_RS_PIB = 'rs_pib'; + const TYPE_RU_INN = 'ru_inn'; + const TYPE_RU_KPP = 'ru_kpp'; + const TYPE_SA_VAT = 'sa_vat'; + const TYPE_SG_GST = 'sg_gst'; + const TYPE_SG_UEN = 'sg_uen'; + const TYPE_SI_TIN = 'si_tin'; + const TYPE_SV_NIT = 'sv_nit'; + const TYPE_TH_VAT = 'th_vat'; + const TYPE_TR_TIN = 'tr_tin'; + const TYPE_TW_VAT = 'tw_vat'; + const TYPE_UA_VAT = 'ua_vat'; + const TYPE_UNKNOWN = 'unknown'; + const TYPE_US_EIN = 'us_ein'; + const TYPE_UY_RUC = 'uy_ruc'; + const TYPE_VE_RIF = 've_rif'; + const TYPE_VN_TIN = 'vn_tin'; + const TYPE_ZA_VAT = 'za_vat'; + + /** + * Creates a new account or customer tax_id object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes an existing account or customer tax_id object. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of tax IDs. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TaxId> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves an account or customer tax_id object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxId + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + const VERIFICATION_STATUS_PENDING = 'pending'; + const VERIFICATION_STATUS_UNAVAILABLE = 'unavailable'; + const VERIFICATION_STATUS_UNVERIFIED = 'unverified'; + const VERIFICATION_STATUS_VERIFIED = 'verified'; +} diff --git a/libraries/Stripe/lib/TaxRate.php b/libraries/Stripe/lib/TaxRate.php new file mode 100644 index 00000000000..631f03d75e3 --- /dev/null +++ b/libraries/Stripe/lib/TaxRate.php @@ -0,0 +1,136 @@ +invoices, subscriptions and Checkout Sessions to collect tax. + * + * Related guide: Tax rates + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $active Defaults to true. When set to false, this tax rate cannot be used with new applications or Checkout Sessions, but will still work for subscriptions and invoices that already have it set. + * @property null|string $country Two-letter country code (ISO 3166-1 alpha-2). + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $description An arbitrary string attached to the tax rate for your internal use only. It will not be visible to your customers. + * @property string $display_name The display name of the tax rates as it will appear to your customer on their receipt email, PDF, and the hosted invoice page. + * @property null|float $effective_percentage Actual/effective tax rate percentage out of 100. For tax calculations with automatic_tax[enabled]=true, this percentage reflects the rate actually used to calculate tax based on the product's taxability and whether the user is registered to collect taxes in the corresponding jurisdiction. + * @property bool $inclusive This specifies if the tax rate is inclusive or exclusive. + * @property null|string $jurisdiction The jurisdiction for the tax rate. You can use this label field for tax reporting purposes. It also appears on your customer’s invoice. + * @property null|string $jurisdiction_level The level of the jurisdiction that imposes this tax rate. Will be null for manually defined tax rates. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property float $percentage Tax rate percentage out of 100. For tax calculations with automatic_tax[enabled]=true, this percentage includes the statutory tax rate of non-taxable jurisdictions. + * @property null|string $state ISO 3166-2 subdivision code, without country prefix. For example, "NY" for New York, United States. + * @property null|string $tax_type The high-level tax type, such as vat or sales_tax. + */ +class TaxRate extends ApiResource +{ + const OBJECT_NAME = 'tax_rate'; + + use ApiOperations\Update; + + const JURISDICTION_LEVEL_CITY = 'city'; + const JURISDICTION_LEVEL_COUNTRY = 'country'; + const JURISDICTION_LEVEL_COUNTY = 'county'; + const JURISDICTION_LEVEL_DISTRICT = 'district'; + const JURISDICTION_LEVEL_MULTIPLE = 'multiple'; + const JURISDICTION_LEVEL_STATE = 'state'; + + const TAX_TYPE_AMUSEMENT_TAX = 'amusement_tax'; + const TAX_TYPE_COMMUNICATIONS_TAX = 'communications_tax'; + const TAX_TYPE_GST = 'gst'; + const TAX_TYPE_HST = 'hst'; + const TAX_TYPE_IGST = 'igst'; + const TAX_TYPE_JCT = 'jct'; + const TAX_TYPE_LEASE_TAX = 'lease_tax'; + const TAX_TYPE_PST = 'pst'; + const TAX_TYPE_QST = 'qst'; + const TAX_TYPE_RST = 'rst'; + const TAX_TYPE_SALES_TAX = 'sales_tax'; + const TAX_TYPE_VAT = 'vat'; + + /** + * Creates a new tax rate. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxRate the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of your tax rates. Tax rates are returned sorted by creation + * date, with the most recently created tax rates appearing first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TaxRate> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a tax rate with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxRate + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates an existing tax rate. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TaxRate the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Terminal/Configuration.php b/libraries/Stripe/lib/Terminal/Configuration.php new file mode 100644 index 00000000000..90f1d9210ef --- /dev/null +++ b/libraries/Stripe/lib/Terminal/Configuration.php @@ -0,0 +1,129 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $name String indicating the name of the Configuration object, set by the user + * @property null|\EDD\Vendor\Stripe\StripeObject $offline + * @property null|\EDD\Vendor\Stripe\StripeObject $reboot_window + * @property null|\EDD\Vendor\Stripe\StripeObject $stripe_s700 + * @property null|\EDD\Vendor\Stripe\StripeObject $tipping + * @property null|\EDD\Vendor\Stripe\StripeObject $verifone_p400 + */ +class Configuration extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'terminal.configuration'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + /** + * Creates a new Configuration object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Configuration the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes a Configuration object. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Configuration the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of Configuration objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Terminal\Configuration> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a Configuration object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Configuration + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a new Configuration object. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Configuration the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Terminal/ConnectionToken.php b/libraries/Stripe/lib/Terminal/ConnectionToken.php new file mode 100644 index 00000000000..64d496cffaf --- /dev/null +++ b/libraries/Stripe/lib/Terminal/ConnectionToken.php @@ -0,0 +1,43 @@ +Fleet management + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $location The id of the location that this connection token is scoped to. Note that location scoping only applies to internet-connected readers. For more details, see the docs on scoping connection tokens. + * @property string $secret Your application should pass this token to the EDD\Vendor\Stripe Terminal SDK. + */ +class ConnectionToken extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'terminal.connection_token'; + + /** + * To connect to a reader the EDD\Vendor\Stripe Terminal SDK needs to retrieve a short-lived + * connection token from Stripe, proxied through your server. On your backend, add + * an endpoint that creates and returns a connection token. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\ConnectionToken the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Terminal/Location.php b/libraries/Stripe/lib/Terminal/Location.php new file mode 100644 index 00000000000..2ef0edded4a --- /dev/null +++ b/libraries/Stripe/lib/Terminal/Location.php @@ -0,0 +1,130 @@ +Fleet management + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property \EDD\Vendor\Stripe\StripeObject $address + * @property null|string $configuration_overrides The ID of a configuration that will be used to customize all readers in this location. + * @property string $display_name The display name of the location. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + */ +class Location extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'terminal.location'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + /** + * Creates a new Location object. For further details, including which + * address fields are required in each country, see the Manage locations guide. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Location the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes a Location object. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Location the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of Location objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Terminal\Location> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a Location object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Location + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a Location object by setting the values of the parameters + * passed. Any parameters not provided will be left unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Location the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/Terminal/Reader.php b/libraries/Stripe/lib/Terminal/Reader.php new file mode 100644 index 00000000000..93dd527f234 --- /dev/null +++ b/libraries/Stripe/lib/Terminal/Reader.php @@ -0,0 +1,230 @@ +Connecting to a reader + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $action The most recent action performed by the reader. + * @property null|string $device_sw_version The current software version of the reader. + * @property string $device_type Type of reader, one of bbpos_wisepad3, stripe_m2, stripe_s700, bbpos_chipper2x, bbpos_wisepos_e, verifone_P400, simulated_wisepos_e, or mobile_phone_reader. + * @property null|string $ip_address The local IP address of the reader. + * @property string $label Custom label given to the reader for easier identification. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string|\EDD\Vendor\Stripe\Terminal\Location $location The location identifier of the reader. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $serial_number Serial number of the reader. + * @property null|string $status The networking status of the reader. We do not recommend using this field in flows that may block taking payments. + */ +class Reader extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'terminal.reader'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const DEVICE_TYPE_BBPOS_CHIPPER2X = 'bbpos_chipper2x'; + const DEVICE_TYPE_BBPOS_WISEPAD3 = 'bbpos_wisepad3'; + const DEVICE_TYPE_BBPOS_WISEPOS_E = 'bbpos_wisepos_e'; + const DEVICE_TYPE_MOBILE_PHONE_READER = 'mobile_phone_reader'; + const DEVICE_TYPE_SIMULATED_WISEPOS_E = 'simulated_wisepos_e'; + const DEVICE_TYPE_STRIPE_M2 = 'stripe_m2'; + const DEVICE_TYPE_STRIPE_S700 = 'stripe_s700'; + const DEVICE_TYPE_VERIFONE_P400 = 'verifone_P400'; + + const STATUS_OFFLINE = 'offline'; + const STATUS_ONLINE = 'online'; + + /** + * Creates a new Reader object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes a Reader object. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of Reader objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Terminal\Reader> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a Reader object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates a Reader object by setting the values of the parameters + * passed. Any parameters not provided will be left unchanged. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader the canceled reader + */ + public function cancelAction($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel_action'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader the processed reader + */ + public function processPaymentIntent($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/process_payment_intent'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader the processed reader + */ + public function processSetupIntent($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/process_setup_intent'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader the refunded reader + */ + public function refundPayment($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/refund_payment'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Terminal\Reader the seted reader + */ + public function setReaderDisplay($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/set_reader_display'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/TestHelpers/TestClock.php b/libraries/Stripe/lib/TestHelpers/TestClock.php new file mode 100644 index 00000000000..67044394349 --- /dev/null +++ b/libraries/Stripe/lib/TestHelpers/TestClock.php @@ -0,0 +1,125 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|string $name The custom name supplied at creation. + * @property string $status The status of the Test Clock. + * @property \EDD\Vendor\Stripe\StripeObject $status_details + */ +class TestClock extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'test_helpers.test_clock'; + + const STATUS_ADVANCING = 'advancing'; + const STATUS_INTERNAL_FAILURE = 'internal_failure'; + const STATUS_READY = 'ready'; + + /** + * Creates a new test clock that can be attached to new customers and quotes. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TestHelpers\TestClock the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Deletes a test clock. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TestHelpers\TestClock the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of your test clocks. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TestHelpers\TestClock> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a test clock. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TestHelpers\TestClock + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TestHelpers\TestClock the advanced test clock + */ + public function advance($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/advance'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Token.php b/libraries/Stripe/lib/Token.php new file mode 100644 index 00000000000..767c520246b --- /dev/null +++ b/libraries/Stripe/lib/Token.php @@ -0,0 +1,93 @@ +recommended payments integrations to perform this process + * on the client-side. This guarantees that no sensitive card data touches your server, + * and allows your integration to operate in a PCI-compliant way. + * + * If you can't use client-side tokenization, you can also create tokens using + * the API with either your publishable or secret API key. If + * your integration uses this method, you're responsible for any PCI compliance + * that it might require, and you must keep your secret API key safe. Unlike with + * client-side tokenization, your customer's information isn't sent directly to + * Stripe, so we can't determine how it's handled or stored. + * + * You can't store or use tokens more than once. To store card or bank account + * information for later use, create Customer + * objects or External accounts. + * Radar, our integrated solution for automatic fraud protection, + * performs best with integrations that use client-side tokenization. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\BankAccount $bank_account

    These bank accounts are payment methods on Customer objects.

    On the other hand External Accounts are transfer destinations on Account objects for connected accounts. They can be bank accounts or debit cards as well, and are documented in the links above.

    Related guide: Bank debits and transfers

    + * @property null|\EDD\Vendor\Stripe\Card $card

    You can store multiple cards on a customer in order to charge the customer later. You can also store multiple debit cards on a recipient in order to transfer to those cards later.

    Related guide: Card payments with Sources

    + * @property null|string $client_ip IP address of the client that generates the token. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $type Type of the token: account, bank_account, card, or pii. + * @property bool $used Determines if you have already used this token (you can only use tokens once). + */ +class Token extends ApiResource +{ + const OBJECT_NAME = 'token'; + + const TYPE_ACCOUNT = 'account'; + const TYPE_BANK_ACCOUNT = 'bank_account'; + const TYPE_CARD = 'card'; + const TYPE_PII = 'pii'; + + /** + * Creates a single-use token that represents a bank account’s details. You can use + * this token with any API method in place of a bank account dictionary. You can + * only use this token once. To do so, attach it to a connected + * account where controller.requirement_collection + * is application, which includes Custom accounts. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Token the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Retrieves the token with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Token + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Topup.php b/libraries/Stripe/lib/Topup.php new file mode 100644 index 00000000000..1cafc13e772 --- /dev/null +++ b/libraries/Stripe/lib/Topup.php @@ -0,0 +1,143 @@ +Topping up your platform account + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount transferred. + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $balance_transaction ID of the balance transaction that describes the impact of this top-up on your account balance. May not be specified depending on status of top-up. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|int $expected_availability_date Date the funds are expected to arrive in your EDD\Vendor\Stripe account for payouts. This factors in delays like weekends or bank holidays. May not be specified depending on status of top-up. + * @property null|string $failure_code Error code explaining reason for top-up failure if available (see the errors section for a list of codes). + * @property null|string $failure_message Message to user further explaining reason for top-up failure if available. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\Source $source The source field is deprecated. It might not always be present in the API response. + * @property null|string $statement_descriptor Extra information about a top-up. This will appear on your source's bank statement. It must contain at least one letter. + * @property string $status The status of the top-up is either canceled, failed, pending, reversed, or succeeded. + * @property null|string $transfer_group A string that identifies this top-up as part of a group. + */ +class Topup extends ApiResource +{ + const OBJECT_NAME = 'topup'; + + use ApiOperations\Update; + + const STATUS_CANCELED = 'canceled'; + const STATUS_FAILED = 'failed'; + const STATUS_PENDING = 'pending'; + const STATUS_REVERSED = 'reversed'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * Top up the balance of an account. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Topup the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of top-ups. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Topup> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a top-up that has previously been created. Supply the + * unique top-up ID that was returned from your previous request, and EDD\Vendor\Stripe will + * return the corresponding top-up information. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Topup + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the metadata of a top-up. Other top-up details are not editable by + * design. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Topup the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Topup the canceled topup + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Transfer.php b/libraries/Stripe/lib/Transfer.php new file mode 100644 index 00000000000..92b7b58614d --- /dev/null +++ b/libraries/Stripe/lib/Transfer.php @@ -0,0 +1,197 @@ +Transfer object is created when you move funds between EDD\Vendor\Stripe accounts as + * part of Connect. + * + * Before April 6, 2017, transfers also represented movement of funds from a + * EDD\Vendor\Stripe account to a card or bank account. This behavior has since been split + * out into a Payout object, with corresponding payout endpoints. For more + * information, read about the + * transfer/payout split. + * + * Related guide: Creating separate charges and transfers + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount in cents (or local equivalent) to be transferred. + * @property int $amount_reversed Amount in cents (or local equivalent) reversed (can be less than the amount attribute on the transfer if a partial reversal was issued). + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $balance_transaction Balance transaction that describes the impact of this transfer on your account balance. + * @property int $created Time that this record of the transfer was first created. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|string|\EDD\Vendor\Stripe\Account $destination ID of the EDD\Vendor\Stripe account the transfer was sent to. + * @property null|string|\EDD\Vendor\Stripe\Charge $destination_payment If the destination is a EDD\Vendor\Stripe account, this will be the ID of the payment that the destination account received for the transfer. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TransferReversal> $reversals A list of reversals that have been applied to the transfer. + * @property bool $reversed Whether the transfer has been fully reversed. If the transfer is only partially reversed, this attribute will still be false. + * @property null|string|\EDD\Vendor\Stripe\Charge $source_transaction ID of the charge that was used to fund the transfer. If null, the transfer was funded from the available balance. + * @property null|string $source_type The source balance this transfer came from. One of card, fpx, or bank_account. + * @property null|string $transfer_group A string that identifies this transaction as part of a group. See the Connect documentation for details. + */ +class Transfer extends ApiResource +{ + const OBJECT_NAME = 'transfer'; + + use ApiOperations\NestedResource; + use ApiOperations\Update; + + const SOURCE_TYPE_BANK_ACCOUNT = 'bank_account'; + const SOURCE_TYPE_CARD = 'card'; + const SOURCE_TYPE_FPX = 'fpx'; + + /** + * To send funds from your EDD\Vendor\Stripe account to a connected account, you create a new + * transfer object. Your EDD\Vendor\Stripe balance must be able to + * cover the transfer amount, or you’ll receive an “Insufficient Funds” error. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Transfer the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of existing transfers sent to connected accounts. The transfers + * are returned in sorted order, with the most recently created transfers appearing + * first. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Transfer> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing transfer. Supply the unique transfer ID + * from either a transfer creation request or the transfer list, and EDD\Vendor\Stripe will + * return the corresponding transfer information. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Transfer + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the specified transfer by setting the values of the parameters passed. + * Any parameters not provided will be left unchanged. + * + * This request accepts only metadata as an argument. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Transfer the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + const PATH_REVERSALS = '/reversals'; + + /** + * @param string $id the ID of the transfer on which to retrieve the transfer reversals + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\TransferReversal> the list of transfer reversals + */ + public static function allReversals($id, $params = null, $opts = null) + { + return self::_allNestedResources($id, static::PATH_REVERSALS, $params, $opts); + } + + /** + * @param string $id the ID of the transfer on which to create the transfer reversal + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TransferReversal + */ + public static function createReversal($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_REVERSALS, $params, $opts); + } + + /** + * @param string $id the ID of the transfer to which the transfer reversal belongs + * @param string $reversalId the ID of the transfer reversal to retrieve + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TransferReversal + */ + public static function retrieveReversal($id, $reversalId, $params = null, $opts = null) + { + return self::_retrieveNestedResource($id, static::PATH_REVERSALS, $reversalId, $params, $opts); + } + + /** + * @param string $id the ID of the transfer to which the transfer reversal belongs + * @param string $reversalId the ID of the transfer reversal to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\TransferReversal + */ + public static function updateReversal($id, $reversalId, $params = null, $opts = null) + { + return self::_updateNestedResource($id, static::PATH_REVERSALS, $reversalId, $params, $opts); + } +} diff --git a/libraries/Stripe/lib/TransferReversal.php b/libraries/Stripe/lib/TransferReversal.php new file mode 100644 index 00000000000..8c076dea126 --- /dev/null +++ b/libraries/Stripe/lib/TransferReversal.php @@ -0,0 +1,76 @@ +EDD\Vendor\Stripe Connect platforms can reverse transfers made to a + * connected account, either entirely or partially, and can also specify whether + * to refund any related application fees. Transfer reversals add to the + * platform's balance and subtract from the destination account's balance. + * + * Reversing a transfer that was made for a destination + * charge is allowed only up to the amount of + * the charge. It is possible to reverse a + * transfer_group + * transfer only if the destination account has enough balance to cover the + * reversal. + * + * Related guide: Reverse transfers + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount, in cents (or local equivalent). + * @property null|string|\EDD\Vendor\Stripe\BalanceTransaction $balance_transaction Balance transaction that describes the impact on your account balance. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string|\EDD\Vendor\Stripe\Refund $destination_payment_refund Linked payment refund for the transfer reversal. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string|\EDD\Vendor\Stripe\Refund $source_refund ID of the refund responsible for the transfer reversal. + * @property string|\EDD\Vendor\Stripe\Transfer $transfer ID of the transfer that was reversed. + */ +class TransferReversal extends ApiResource +{ + const OBJECT_NAME = 'transfer_reversal'; + + use ApiOperations\Update { + save as protected _save; + } + + /** + * @return string the API URL for this EDD\Vendor\Stripe transfer reversal + */ + public function instanceUrl() + { + $id = $this['id']; + $transfer = $this['transfer']; + if (!$id) { + throw new Exception\UnexpectedValueException( + 'Could not determine which URL to request: ' . + "class instance has invalid ID: {$id}", + null + ); + } + $id = Util\Util::utf8($id); + $transfer = Util\Util::utf8($transfer); + + $base = Transfer::classUrl(); + $transferExtn = \urlencode($transfer); + $extn = \urlencode($id); + + return "{$base}/{$transferExtn}/reversals/{$extn}"; + } + + /** + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return TransferReversal the saved reversal + */ + public function save($opts = null) + { + return $this->_save($opts); + } +} diff --git a/libraries/Stripe/lib/Treasury/CreditReversal.php b/libraries/Stripe/lib/Treasury/CreditReversal.php new file mode 100644 index 00000000000..4ebacc7f0c7 --- /dev/null +++ b/libraries/Stripe/lib/Treasury/CreditReversal.php @@ -0,0 +1,95 @@ +ReceivedCredits depending on their network and source flow. Reversing a ReceivedCredit leads to the creation of a new object known as a CreditReversal. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in cents) transferred. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string $financial_account The FinancialAccount to reverse funds from. + * @property null|string $hosted_regulatory_receipt_url A hosted transaction receipt URL that is provided when money movement is considered regulated under Stripe's money transmission licenses. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $network The rails used to reverse the funds. + * @property string $received_credit The ReceivedCredit being reversed. + * @property string $status Status of the CreditReversal + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property null|string|\EDD\Vendor\Stripe\Treasury\Transaction $transaction The Transaction associated with this object. + */ +class CreditReversal extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.credit_reversal'; + + const NETWORK_ACH = 'ach'; + const NETWORK_STRIPE = 'stripe'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_POSTED = 'posted'; + const STATUS_PROCESSING = 'processing'; + + /** + * Reverses a ReceivedCredit and creates a CreditReversal object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\CreditReversal the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of CreditReversals. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\CreditReversal> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing CreditReversal by passing the unique + * CreditReversal ID from either the CreditReversal creation request or + * CreditReversal list. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\CreditReversal + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Treasury/DebitReversal.php b/libraries/Stripe/lib/Treasury/DebitReversal.php new file mode 100644 index 00000000000..71304879177 --- /dev/null +++ b/libraries/Stripe/lib/Treasury/DebitReversal.php @@ -0,0 +1,94 @@ +ReceivedDebits depending on their network and source flow. Reversing a ReceivedDebit leads to the creation of a new object known as a DebitReversal. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in cents) transferred. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $financial_account The FinancialAccount to reverse funds from. + * @property null|string $hosted_regulatory_receipt_url A hosted transaction receipt URL that is provided when money movement is considered regulated under Stripe's money transmission licenses. + * @property null|\EDD\Vendor\Stripe\StripeObject $linked_flows Other flows linked to a DebitReversal. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $network The rails used to reverse the funds. + * @property string $received_debit The ReceivedDebit being reversed. + * @property string $status Status of the DebitReversal + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property null|string|\EDD\Vendor\Stripe\Treasury\Transaction $transaction The Transaction associated with this object. + */ +class DebitReversal extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.debit_reversal'; + + const NETWORK_ACH = 'ach'; + const NETWORK_CARD = 'card'; + + const STATUS_FAILED = 'failed'; + const STATUS_PROCESSING = 'processing'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * Reverses a ReceivedDebit and creates a DebitReversal object. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\DebitReversal the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of DebitReversals. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\DebitReversal> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a DebitReversal object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\DebitReversal + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Treasury/FinancialAccount.php b/libraries/Stripe/lib/Treasury/FinancialAccount.php new file mode 100644 index 00000000000..c55b772a810 --- /dev/null +++ b/libraries/Stripe/lib/Treasury/FinancialAccount.php @@ -0,0 +1,153 @@ +ISO 3166-1 alpha-2). + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|\EDD\Vendor\Stripe\Treasury\FinancialAccountFeatures $features Encodes whether a FinancialAccount has access to a particular Feature, with a status enum and associated status_details. EDD\Vendor\Stripe or the platform can control Features via the requested field. + * @property \EDD\Vendor\Stripe\StripeObject[] $financial_addresses The set of credentials that resolve to a FinancialAccount. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property null|\EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string[] $pending_features The array of paths to pending Features in the Features hash. + * @property null|\EDD\Vendor\Stripe\StripeObject $platform_restrictions The set of functionalities that the platform can restrict on the FinancialAccount. + * @property null|string[] $restricted_features The array of paths to restricted Features in the Features hash. + * @property string $status The enum specifying what state the account is in. + * @property \EDD\Vendor\Stripe\StripeObject $status_details + * @property string[] $supported_currencies The currencies the FinancialAccount can hold a balance in. Three-letter ISO currency code, in lowercase. + */ +class FinancialAccount extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.financial_account'; + + use \EDD\Vendor\Stripe\ApiOperations\Update; + + const STATUS_CLOSED = 'closed'; + const STATUS_OPEN = 'open'; + + /** + * Creates a new FinancialAccount. For now, each connected account can only have + * one FinancialAccount. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccount the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of FinancialAccounts. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\FinancialAccount> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of a FinancialAccount. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccount + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the details of a FinancialAccount. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccount the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccountFeatures the retrieved financial account features + */ + public function retrieveFeatures($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/features'; + list($response, $opts) = $this->_request('get', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\FinancialAccountFeatures the updated financial account features + */ + public function updateFeatures($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/features'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Treasury/FinancialAccountFeatures.php b/libraries/Stripe/lib/Treasury/FinancialAccountFeatures.php new file mode 100644 index 00000000000..9127d7f4e21 --- /dev/null +++ b/libraries/Stripe/lib/Treasury/FinancialAccountFeatures.php @@ -0,0 +1,23 @@ +status enum and associated status_details. + * EDD\Vendor\Stripe or the platform can control Features via the requested field. + * + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|\EDD\Vendor\Stripe\StripeObject $card_issuing Toggle settings for enabling/disabling a feature + * @property null|\EDD\Vendor\Stripe\StripeObject $deposit_insurance Toggle settings for enabling/disabling a feature + * @property null|\EDD\Vendor\Stripe\StripeObject $financial_addresses Settings related to Financial Addresses features on a Financial Account + * @property null|\EDD\Vendor\Stripe\StripeObject $inbound_transfers InboundTransfers contains inbound transfers features for a FinancialAccount. + * @property null|\EDD\Vendor\Stripe\StripeObject $intra_stripe_flows Toggle settings for enabling/disabling a feature + * @property null|\EDD\Vendor\Stripe\StripeObject $outbound_payments Settings related to Outbound Payments features on a Financial Account + * @property null|\EDD\Vendor\Stripe\StripeObject $outbound_transfers OutboundTransfers contains outbound transfers features for a FinancialAccount. + */ +class FinancialAccountFeatures extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.financial_account_features'; +} diff --git a/libraries/Stripe/lib/Treasury/InboundTransfer.php b/libraries/Stripe/lib/Treasury/InboundTransfer.php new file mode 100644 index 00000000000..9d9ee50477e --- /dev/null +++ b/libraries/Stripe/lib/Treasury/InboundTransfer.php @@ -0,0 +1,116 @@ +InboundTransfers to add funds to your FinancialAccount via a PaymentMethod that is owned by you. The funds will be transferred via an ACH debit. + * + * Related guide: Moving money with Treasury using InboundTransfer objects + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in cents) transferred. + * @property bool $cancelable Returns true if the InboundTransfer is able to be canceled. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|\EDD\Vendor\Stripe\StripeObject $failure_details Details about this InboundTransfer's failure. Only set when status is failed. + * @property string $financial_account The FinancialAccount that received the funds. + * @property null|string $hosted_regulatory_receipt_url A hosted transaction receipt URL that is provided when money movement is considered regulated under Stripe's money transmission licenses. + * @property \EDD\Vendor\Stripe\StripeObject $linked_flows + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property string $origin_payment_method The origin payment method to be debited for an InboundTransfer. + * @property null|\EDD\Vendor\Stripe\StripeObject $origin_payment_method_details Details about the PaymentMethod for an InboundTransfer. + * @property null|bool $returned Returns true if the funds for an InboundTransfer were returned after the InboundTransfer went to the succeeded state. + * @property string $statement_descriptor Statement descriptor shown when funds are debited from the source. Not all payment networks support statement_descriptor. + * @property string $status Status of the InboundTransfer: processing, succeeded, failed, and canceled. An InboundTransfer is processing if it is created and pending. The status changes to succeeded once the funds have been "confirmed" and a transaction is created and posted. The status changes to failed if the transfer fails. + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property null|string|\EDD\Vendor\Stripe\Treasury\Transaction $transaction The Transaction associated with this object. + */ +class InboundTransfer extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.inbound_transfer'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_FAILED = 'failed'; + const STATUS_PROCESSING = 'processing'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * Creates an InboundTransfer. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of InboundTransfers sent from the specified FinancialAccount. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\InboundTransfer> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing InboundTransfer. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\InboundTransfer the canceled inbound transfer + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Treasury/OutboundPayment.php b/libraries/Stripe/lib/Treasury/OutboundPayment.php new file mode 100644 index 00000000000..31e6d25eeb6 --- /dev/null +++ b/libraries/Stripe/lib/Treasury/OutboundPayment.php @@ -0,0 +1,123 @@ +OutboundPayments to send funds to another party's external bank account or FinancialAccount. To send money to an account belonging to the same user, use an OutboundTransfer. + * + * Simulate OutboundPayment state changes with the /v1/test_helpers/treasury/outbound_payments endpoints. These methods can only be called on test mode objects. + * + * Related guide: Moving money with Treasury using OutboundPayment objects + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in cents) transferred. + * @property bool $cancelable Returns true if the object can be canceled, and false otherwise. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $customer ID of the customer to whom an OutboundPayment is sent. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|string $destination_payment_method The PaymentMethod via which an OutboundPayment is sent. This field can be empty if the OutboundPayment was created using destination_payment_method_data. + * @property null|\EDD\Vendor\Stripe\StripeObject $destination_payment_method_details Details about the PaymentMethod for an OutboundPayment. + * @property null|\EDD\Vendor\Stripe\StripeObject $end_user_details Details about the end user. + * @property int $expected_arrival_date The date when funds are expected to arrive in the destination account. + * @property string $financial_account The FinancialAccount that funds were pulled from. + * @property null|string $hosted_regulatory_receipt_url A hosted transaction receipt URL that is provided when money movement is considered regulated under Stripe's money transmission licenses. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $returned_details Details about a returned OutboundPayment. Only set when the status is returned. + * @property string $statement_descriptor The description that appears on the receiving end for an OutboundPayment (for example, bank statement for external bank transfer). + * @property string $status Current status of the OutboundPayment: processing, failed, posted, returned, canceled. An OutboundPayment is processing if it has been created and is pending. The status changes to posted once the OutboundPayment has been "confirmed" and funds have left the account, or to failed or canceled. If an OutboundPayment fails to arrive at its destination, its status will change to returned. + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property null|\EDD\Vendor\Stripe\StripeObject $tracking_details Details about network-specific tracking information if available. + * @property string|\EDD\Vendor\Stripe\Treasury\Transaction $transaction The Transaction associated with this object. + */ +class OutboundPayment extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.outbound_payment'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_FAILED = 'failed'; + const STATUS_POSTED = 'posted'; + const STATUS_PROCESSING = 'processing'; + const STATUS_RETURNED = 'returned'; + + /** + * Creates an OutboundPayment. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of OutboundPayments sent from the specified FinancialAccount. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\OutboundPayment> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing OutboundPayment by passing the unique + * OutboundPayment ID from either the OutboundPayment creation request or + * OutboundPayment list. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundPayment the canceled outbound payment + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Treasury/OutboundTransfer.php b/libraries/Stripe/lib/Treasury/OutboundTransfer.php new file mode 100644 index 00000000000..0d6979e887c --- /dev/null +++ b/libraries/Stripe/lib/Treasury/OutboundTransfer.php @@ -0,0 +1,121 @@ +OutboundTransfers to transfer funds from a FinancialAccount to a PaymentMethod belonging to the same entity. To send funds to a different party, use OutboundPayments instead. You can send funds over ACH rails or through a domestic wire transfer to a user's own external bank account. + * + * Simulate OutboundTransfer state changes with the /v1/test_helpers/treasury/outbound_transfers endpoints. These methods can only be called on test mode objects. + * + * Related guide: Moving money with Treasury using OutboundTransfer objects + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in cents) transferred. + * @property bool $cancelable Returns true if the object can be canceled, and false otherwise. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property null|string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|string $destination_payment_method The PaymentMethod used as the payment instrument for an OutboundTransfer. + * @property \EDD\Vendor\Stripe\StripeObject $destination_payment_method_details + * @property int $expected_arrival_date The date when funds are expected to arrive in the destination account. + * @property string $financial_account The FinancialAccount that funds were pulled from. + * @property null|string $hosted_regulatory_receipt_url A hosted transaction receipt URL that is provided when money movement is considered regulated under Stripe's money transmission licenses. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|\EDD\Vendor\Stripe\StripeObject $returned_details Details about a returned OutboundTransfer. Only set when the status is returned. + * @property string $statement_descriptor Information about the OutboundTransfer to be sent to the recipient account. + * @property string $status Current status of the OutboundTransfer: processing, failed, canceled, posted, returned. An OutboundTransfer is processing if it has been created and is pending. The status changes to posted once the OutboundTransfer has been "confirmed" and funds have left the account, or to failed or canceled. If an OutboundTransfer fails to arrive at its destination, its status will change to returned. + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + * @property null|\EDD\Vendor\Stripe\StripeObject $tracking_details Details about network-specific tracking information if available. + * @property string|\EDD\Vendor\Stripe\Treasury\Transaction $transaction The Transaction associated with this object. + */ +class OutboundTransfer extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.outbound_transfer'; + + const STATUS_CANCELED = 'canceled'; + const STATUS_FAILED = 'failed'; + const STATUS_POSTED = 'posted'; + const STATUS_PROCESSING = 'processing'; + const STATUS_RETURNED = 'returned'; + + /** + * Creates an OutboundTransfer. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * Returns a list of OutboundTransfers sent from the specified FinancialAccount. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\OutboundTransfer> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing OutboundTransfer by passing the unique + * OutboundTransfer ID from either the OutboundTransfer creation request or + * OutboundTransfer list. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\OutboundTransfer the canceled outbound transfer + */ + public function cancel($params = null, $opts = null) + { + $url = $this->instanceUrl() . '/cancel'; + list($response, $opts) = $this->_request('post', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } +} diff --git a/libraries/Stripe/lib/Treasury/ReceivedCredit.php b/libraries/Stripe/lib/Treasury/ReceivedCredit.php new file mode 100644 index 00000000000..3f109cfb097 --- /dev/null +++ b/libraries/Stripe/lib/Treasury/ReceivedCredit.php @@ -0,0 +1,79 @@ +FinancialAccount (for example, via ACH or wire). These money movements are not initiated from the FinancialAccount. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in cents) transferred. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|string $failure_code Reason for the failure. A ReceivedCredit might fail because the receiving FinancialAccount is closed or frozen. + * @property null|string $financial_account The FinancialAccount that received the funds. + * @property null|string $hosted_regulatory_receipt_url A hosted transaction receipt URL that is provided when money movement is considered regulated under Stripe's money transmission licenses. + * @property \EDD\Vendor\Stripe\StripeObject $initiating_payment_method_details + * @property \EDD\Vendor\Stripe\StripeObject $linked_flows + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $network The rails used to send the funds. + * @property null|\EDD\Vendor\Stripe\StripeObject $reversal_details Details describing when a ReceivedCredit may be reversed. + * @property string $status Status of the ReceivedCredit. ReceivedCredits are created either succeeded (approved) or failed (declined). If a ReceivedCredit is declined, the failure reason can be found in the failure_code field. + * @property null|string|\EDD\Vendor\Stripe\Treasury\Transaction $transaction The Transaction associated with this object. + */ +class ReceivedCredit extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.received_credit'; + + const FAILURE_CODE_ACCOUNT_CLOSED = 'account_closed'; + const FAILURE_CODE_ACCOUNT_FROZEN = 'account_frozen'; + const FAILURE_CODE_OTHER = 'other'; + + const NETWORK_ACH = 'ach'; + const NETWORK_CARD = 'card'; + const NETWORK_STRIPE = 'stripe'; + const NETWORK_US_DOMESTIC_WIRE = 'us_domestic_wire'; + + const STATUS_FAILED = 'failed'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * Returns a list of ReceivedCredits. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\ReceivedCredit> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing ReceivedCredit by passing the unique + * ReceivedCredit ID from the ReceivedCredit list. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\ReceivedCredit + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Treasury/ReceivedDebit.php b/libraries/Stripe/lib/Treasury/ReceivedDebit.php new file mode 100644 index 00000000000..c12a939f6f5 --- /dev/null +++ b/libraries/Stripe/lib/Treasury/ReceivedDebit.php @@ -0,0 +1,80 @@ +FinancialAccount. These are not initiated from the FinancialAccount. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in cents) transferred. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|string $failure_code Reason for the failure. A ReceivedDebit might fail because the FinancialAccount doesn't have sufficient funds, is closed, or is frozen. + * @property null|string $financial_account The FinancialAccount that funds were pulled from. + * @property null|string $hosted_regulatory_receipt_url A hosted transaction receipt URL that is provided when money movement is considered regulated under Stripe's money transmission licenses. + * @property null|\EDD\Vendor\Stripe\StripeObject $initiating_payment_method_details + * @property \EDD\Vendor\Stripe\StripeObject $linked_flows + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $network The network used for the ReceivedDebit. + * @property null|\EDD\Vendor\Stripe\StripeObject $reversal_details Details describing when a ReceivedDebit might be reversed. + * @property string $status Status of the ReceivedDebit. ReceivedDebits are created with a status of either succeeded (approved) or failed (declined). The failure reason can be found under the failure_code. + * @property null|string|\EDD\Vendor\Stripe\Treasury\Transaction $transaction The Transaction associated with this object. + */ +class ReceivedDebit extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.received_debit'; + + const FAILURE_CODE_ACCOUNT_CLOSED = 'account_closed'; + const FAILURE_CODE_ACCOUNT_FROZEN = 'account_frozen'; + const FAILURE_CODE_INSUFFICIENT_FUNDS = 'insufficient_funds'; + const FAILURE_CODE_INTERNATIONAL_TRANSACTION = 'international_transaction'; + const FAILURE_CODE_OTHER = 'other'; + + const NETWORK_ACH = 'ach'; + const NETWORK_CARD = 'card'; + const NETWORK_STRIPE = 'stripe'; + + const STATUS_FAILED = 'failed'; + const STATUS_SUCCEEDED = 'succeeded'; + + /** + * Returns a list of ReceivedDebits. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\ReceivedDebit> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing ReceivedDebit by passing the unique + * ReceivedDebit ID from the ReceivedDebit list. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\ReceivedDebit + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Treasury/Transaction.php b/libraries/Stripe/lib/Treasury/Transaction.php new file mode 100644 index 00000000000..9a59c266d32 --- /dev/null +++ b/libraries/Stripe/lib/Treasury/Transaction.php @@ -0,0 +1,79 @@ +FinancialAccount's balance. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property int $amount Amount (in cents) transferred. + * @property \EDD\Vendor\Stripe\StripeObject $balance_impact Change to a FinancialAccount's balance + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property string $description An arbitrary string attached to the object. Often useful for displaying to users. + * @property null|\EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\TransactionEntry> $entries A list of TransactionEntries that are part of this Transaction. This cannot be expanded in any list endpoints. + * @property string $financial_account The FinancialAccount associated with this object. + * @property null|string $flow ID of the flow that created the Transaction. + * @property null|\EDD\Vendor\Stripe\StripeObject $flow_details Details of the flow that created the Transaction. + * @property string $flow_type Type of the flow that created the Transaction. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string $status Status of the Transaction. + * @property \EDD\Vendor\Stripe\StripeObject $status_transitions + */ +class Transaction extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.transaction'; + + const FLOW_TYPE_CREDIT_REVERSAL = 'credit_reversal'; + const FLOW_TYPE_DEBIT_REVERSAL = 'debit_reversal'; + const FLOW_TYPE_INBOUND_TRANSFER = 'inbound_transfer'; + const FLOW_TYPE_ISSUING_AUTHORIZATION = 'issuing_authorization'; + const FLOW_TYPE_OTHER = 'other'; + const FLOW_TYPE_OUTBOUND_PAYMENT = 'outbound_payment'; + const FLOW_TYPE_OUTBOUND_TRANSFER = 'outbound_transfer'; + const FLOW_TYPE_RECEIVED_CREDIT = 'received_credit'; + const FLOW_TYPE_RECEIVED_DEBIT = 'received_debit'; + + const STATUS_OPEN = 'open'; + const STATUS_POSTED = 'posted'; + const STATUS_VOID = 'void'; + + /** + * Retrieves a list of Transaction objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\Transaction> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the details of an existing Transaction. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\Transaction + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/Treasury/TransactionEntry.php b/libraries/Stripe/lib/Treasury/TransactionEntry.php new file mode 100644 index 00000000000..80250264afa --- /dev/null +++ b/libraries/Stripe/lib/Treasury/TransactionEntry.php @@ -0,0 +1,94 @@ +Transaction. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property \EDD\Vendor\Stripe\StripeObject $balance_impact Change to a FinancialAccount's balance + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property string $currency Three-letter ISO currency code, in lowercase. Must be a supported currency. + * @property int $effective_at When the TransactionEntry will impact the FinancialAccount's balance. + * @property string $financial_account The FinancialAccount associated with this object. + * @property null|string $flow Token of the flow associated with the TransactionEntry. + * @property null|\EDD\Vendor\Stripe\StripeObject $flow_details Details of the flow associated with the TransactionEntry. + * @property string $flow_type Type of the flow associated with the TransactionEntry. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property string|\EDD\Vendor\Stripe\Treasury\Transaction $transaction The Transaction associated with this object. + * @property string $type The specific money movement that generated the TransactionEntry. + */ +class TransactionEntry extends \EDD\Vendor\Stripe\ApiResource +{ + const OBJECT_NAME = 'treasury.transaction_entry'; + + const FLOW_TYPE_CREDIT_REVERSAL = 'credit_reversal'; + const FLOW_TYPE_DEBIT_REVERSAL = 'debit_reversal'; + const FLOW_TYPE_INBOUND_TRANSFER = 'inbound_transfer'; + const FLOW_TYPE_ISSUING_AUTHORIZATION = 'issuing_authorization'; + const FLOW_TYPE_OTHER = 'other'; + const FLOW_TYPE_OUTBOUND_PAYMENT = 'outbound_payment'; + const FLOW_TYPE_OUTBOUND_TRANSFER = 'outbound_transfer'; + const FLOW_TYPE_RECEIVED_CREDIT = 'received_credit'; + const FLOW_TYPE_RECEIVED_DEBIT = 'received_debit'; + + const TYPE_CREDIT_REVERSAL = 'credit_reversal'; + const TYPE_CREDIT_REVERSAL_POSTING = 'credit_reversal_posting'; + const TYPE_DEBIT_REVERSAL = 'debit_reversal'; + const TYPE_INBOUND_TRANSFER = 'inbound_transfer'; + const TYPE_INBOUND_TRANSFER_RETURN = 'inbound_transfer_return'; + const TYPE_ISSUING_AUTHORIZATION_HOLD = 'issuing_authorization_hold'; + const TYPE_ISSUING_AUTHORIZATION_RELEASE = 'issuing_authorization_release'; + const TYPE_OTHER = 'other'; + const TYPE_OUTBOUND_PAYMENT = 'outbound_payment'; + const TYPE_OUTBOUND_PAYMENT_CANCELLATION = 'outbound_payment_cancellation'; + const TYPE_OUTBOUND_PAYMENT_FAILURE = 'outbound_payment_failure'; + const TYPE_OUTBOUND_PAYMENT_POSTING = 'outbound_payment_posting'; + const TYPE_OUTBOUND_PAYMENT_RETURN = 'outbound_payment_return'; + const TYPE_OUTBOUND_TRANSFER = 'outbound_transfer'; + const TYPE_OUTBOUND_TRANSFER_CANCELLATION = 'outbound_transfer_cancellation'; + const TYPE_OUTBOUND_TRANSFER_FAILURE = 'outbound_transfer_failure'; + const TYPE_OUTBOUND_TRANSFER_POSTING = 'outbound_transfer_posting'; + const TYPE_OUTBOUND_TRANSFER_RETURN = 'outbound_transfer_return'; + const TYPE_RECEIVED_CREDIT = 'received_credit'; + const TYPE_RECEIVED_DEBIT = 'received_debit'; + + /** + * Retrieves a list of TransactionEntry objects. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\Treasury\TransactionEntry> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves a TransactionEntry object. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Treasury\TransactionEntry + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } +} diff --git a/libraries/Stripe/lib/UsageRecord.php b/libraries/Stripe/lib/UsageRecord.php new file mode 100644 index 00000000000..de0604dd88d --- /dev/null +++ b/libraries/Stripe/lib/UsageRecord.php @@ -0,0 +1,25 @@ +Metered billing + * + * This is our legacy usage-based billing API. See the updated usage-based billing docs. + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property int $quantity The usage quantity for the specified date. + * @property string $subscription_item The ID of the subscription item this usage record contains data for. + * @property int $timestamp The timestamp when this usage occurred. + */ +class UsageRecord extends ApiResource +{ + const OBJECT_NAME = 'usage_record'; +} diff --git a/libraries/Stripe/lib/UsageRecordSummary.php b/libraries/Stripe/lib/UsageRecordSummary.php new file mode 100644 index 00000000000..dbc41c29f1c --- /dev/null +++ b/libraries/Stripe/lib/UsageRecordSummary.php @@ -0,0 +1,19 @@ +true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $period + * @property string $subscription_item The ID of the subscription item this summary is describing. + * @property int $total_usage The total usage within this usage period. + */ +class UsageRecordSummary extends ApiResource +{ + const OBJECT_NAME = 'usage_record_summary'; +} diff --git a/libraries/Stripe/lib/Util/ApiVersion.php b/libraries/Stripe/lib/Util/ApiVersion.php new file mode 100644 index 00000000000..3e4911c92cb --- /dev/null +++ b/libraries/Stripe/lib/Util/ApiVersion.php @@ -0,0 +1,10 @@ +container = \array_change_key_case($initial_array, \CASE_LOWER); + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->container); + } + + /** + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->container); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $offset = self::maybeLowercase($offset); + if (null === $offset) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + $offset = self::maybeLowercase($offset); + + return isset($this->container[$offset]); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + $offset = self::maybeLowercase($offset); + unset($this->container[$offset]); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + $offset = self::maybeLowercase($offset); + + return isset($this->container[$offset]) ? $this->container[$offset] : null; + } + + private static function maybeLowercase($v) + { + if (\is_string($v)) { + return \strtolower($v); + } + + return $v; + } +} diff --git a/libraries/Stripe/lib/Util/DefaultLogger.php b/libraries/Stripe/lib/Util/DefaultLogger.php new file mode 100644 index 00000000000..f613791b227 --- /dev/null +++ b/libraries/Stripe/lib/Util/DefaultLogger.php @@ -0,0 +1,29 @@ + 0) { + throw new \EDD\Vendor\Stripe\Exception\BadMethodCallException('DefaultLogger does not currently implement context. Please implement if you need it.'); + } + + if (null === $this->destination) { + \error_log($message, $this->messageType); + } else { + \error_log($message, $this->messageType, $this->destination); + } + } +} diff --git a/libraries/Stripe/lib/Util/LoggerInterface.php b/libraries/Stripe/lib/Util/LoggerInterface.php new file mode 100644 index 00000000000..deaa448a073 --- /dev/null +++ b/libraries/Stripe/lib/Util/LoggerInterface.php @@ -0,0 +1,34 @@ + \EDD\Vendor\Stripe\Collection::class, + \EDD\Vendor\Stripe\Issuing\CardDetails::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\CardDetails::class, + \EDD\Vendor\Stripe\SearchResult::OBJECT_NAME => \EDD\Vendor\Stripe\SearchResult::class, + \EDD\Vendor\Stripe\File::OBJECT_NAME_ALT => \EDD\Vendor\Stripe\File::class, + // object classes: The beginning of the section generated from our OpenAPI spec + \EDD\Vendor\Stripe\Account::OBJECT_NAME => \EDD\Vendor\Stripe\Account::class, + \EDD\Vendor\Stripe\AccountLink::OBJECT_NAME => \EDD\Vendor\Stripe\AccountLink::class, + \EDD\Vendor\Stripe\AccountSession::OBJECT_NAME => \EDD\Vendor\Stripe\AccountSession::class, + \EDD\Vendor\Stripe\ApplePayDomain::OBJECT_NAME => \EDD\Vendor\Stripe\ApplePayDomain::class, + \EDD\Vendor\Stripe\Application::OBJECT_NAME => \EDD\Vendor\Stripe\Application::class, + \EDD\Vendor\Stripe\ApplicationFee::OBJECT_NAME => \EDD\Vendor\Stripe\ApplicationFee::class, + \EDD\Vendor\Stripe\ApplicationFeeRefund::OBJECT_NAME => \EDD\Vendor\Stripe\ApplicationFeeRefund::class, + \EDD\Vendor\Stripe\Apps\Secret::OBJECT_NAME => \EDD\Vendor\Stripe\Apps\Secret::class, + \EDD\Vendor\Stripe\Balance::OBJECT_NAME => \EDD\Vendor\Stripe\Balance::class, + \EDD\Vendor\Stripe\BalanceTransaction::OBJECT_NAME => \EDD\Vendor\Stripe\BalanceTransaction::class, + \EDD\Vendor\Stripe\BankAccount::OBJECT_NAME => \EDD\Vendor\Stripe\BankAccount::class, + \EDD\Vendor\Stripe\Billing\Alert::OBJECT_NAME => \EDD\Vendor\Stripe\Billing\Alert::class, + \EDD\Vendor\Stripe\Billing\AlertTriggered::OBJECT_NAME => \EDD\Vendor\Stripe\Billing\AlertTriggered::class, + \EDD\Vendor\Stripe\Billing\Meter::OBJECT_NAME => \EDD\Vendor\Stripe\Billing\Meter::class, + \EDD\Vendor\Stripe\Billing\MeterEvent::OBJECT_NAME => \EDD\Vendor\Stripe\Billing\MeterEvent::class, + \EDD\Vendor\Stripe\Billing\MeterEventAdjustment::OBJECT_NAME => \EDD\Vendor\Stripe\Billing\MeterEventAdjustment::class, + \EDD\Vendor\Stripe\Billing\MeterEventSummary::OBJECT_NAME => \EDD\Vendor\Stripe\Billing\MeterEventSummary::class, + \EDD\Vendor\Stripe\BillingPortal\Configuration::OBJECT_NAME => \EDD\Vendor\Stripe\BillingPortal\Configuration::class, + \EDD\Vendor\Stripe\BillingPortal\Session::OBJECT_NAME => \EDD\Vendor\Stripe\BillingPortal\Session::class, + \EDD\Vendor\Stripe\Capability::OBJECT_NAME => \EDD\Vendor\Stripe\Capability::class, + \EDD\Vendor\Stripe\Card::OBJECT_NAME => \EDD\Vendor\Stripe\Card::class, + \EDD\Vendor\Stripe\CashBalance::OBJECT_NAME => \EDD\Vendor\Stripe\CashBalance::class, + \EDD\Vendor\Stripe\Charge::OBJECT_NAME => \EDD\Vendor\Stripe\Charge::class, + \EDD\Vendor\Stripe\Checkout\Session::OBJECT_NAME => \EDD\Vendor\Stripe\Checkout\Session::class, + \EDD\Vendor\Stripe\Climate\Order::OBJECT_NAME => \EDD\Vendor\Stripe\Climate\Order::class, + \EDD\Vendor\Stripe\Climate\Product::OBJECT_NAME => \EDD\Vendor\Stripe\Climate\Product::class, + \EDD\Vendor\Stripe\Climate\Supplier::OBJECT_NAME => \EDD\Vendor\Stripe\Climate\Supplier::class, + \EDD\Vendor\Stripe\ConfirmationToken::OBJECT_NAME => \EDD\Vendor\Stripe\ConfirmationToken::class, + \EDD\Vendor\Stripe\ConnectCollectionTransfer::OBJECT_NAME => \EDD\Vendor\Stripe\ConnectCollectionTransfer::class, + \EDD\Vendor\Stripe\CountrySpec::OBJECT_NAME => \EDD\Vendor\Stripe\CountrySpec::class, + \EDD\Vendor\Stripe\Coupon::OBJECT_NAME => \EDD\Vendor\Stripe\Coupon::class, + \EDD\Vendor\Stripe\CreditNote::OBJECT_NAME => \EDD\Vendor\Stripe\CreditNote::class, + \EDD\Vendor\Stripe\CreditNoteLineItem::OBJECT_NAME => \EDD\Vendor\Stripe\CreditNoteLineItem::class, + \EDD\Vendor\Stripe\Customer::OBJECT_NAME => \EDD\Vendor\Stripe\Customer::class, + \EDD\Vendor\Stripe\CustomerBalanceTransaction::OBJECT_NAME => \EDD\Vendor\Stripe\CustomerBalanceTransaction::class, + \EDD\Vendor\Stripe\CustomerCashBalanceTransaction::OBJECT_NAME => \EDD\Vendor\Stripe\CustomerCashBalanceTransaction::class, + \EDD\Vendor\Stripe\CustomerSession::OBJECT_NAME => \EDD\Vendor\Stripe\CustomerSession::class, + \EDD\Vendor\Stripe\Discount::OBJECT_NAME => \EDD\Vendor\Stripe\Discount::class, + \EDD\Vendor\Stripe\Dispute::OBJECT_NAME => \EDD\Vendor\Stripe\Dispute::class, + \EDD\Vendor\Stripe\Entitlements\ActiveEntitlement::OBJECT_NAME => \EDD\Vendor\Stripe\Entitlements\ActiveEntitlement::class, + \EDD\Vendor\Stripe\Entitlements\ActiveEntitlementSummary::OBJECT_NAME => \EDD\Vendor\Stripe\Entitlements\ActiveEntitlementSummary::class, + \EDD\Vendor\Stripe\Entitlements\Feature::OBJECT_NAME => \EDD\Vendor\Stripe\Entitlements\Feature::class, + \EDD\Vendor\Stripe\EphemeralKey::OBJECT_NAME => \EDD\Vendor\Stripe\EphemeralKey::class, + \EDD\Vendor\Stripe\Event::OBJECT_NAME => \EDD\Vendor\Stripe\Event::class, + \EDD\Vendor\Stripe\ExchangeRate::OBJECT_NAME => \EDD\Vendor\Stripe\ExchangeRate::class, + \EDD\Vendor\Stripe\File::OBJECT_NAME => \EDD\Vendor\Stripe\File::class, + \EDD\Vendor\Stripe\FileLink::OBJECT_NAME => \EDD\Vendor\Stripe\FileLink::class, + \EDD\Vendor\Stripe\FinancialConnections\Account::OBJECT_NAME => \EDD\Vendor\Stripe\FinancialConnections\Account::class, + \EDD\Vendor\Stripe\FinancialConnections\AccountOwner::OBJECT_NAME => \EDD\Vendor\Stripe\FinancialConnections\AccountOwner::class, + \EDD\Vendor\Stripe\FinancialConnections\AccountOwnership::OBJECT_NAME => \EDD\Vendor\Stripe\FinancialConnections\AccountOwnership::class, + \EDD\Vendor\Stripe\FinancialConnections\Session::OBJECT_NAME => \EDD\Vendor\Stripe\FinancialConnections\Session::class, + \EDD\Vendor\Stripe\FinancialConnections\Transaction::OBJECT_NAME => \EDD\Vendor\Stripe\FinancialConnections\Transaction::class, + \EDD\Vendor\Stripe\Forwarding\Request::OBJECT_NAME => \EDD\Vendor\Stripe\Forwarding\Request::class, + \EDD\Vendor\Stripe\FundingInstructions::OBJECT_NAME => \EDD\Vendor\Stripe\FundingInstructions::class, + \EDD\Vendor\Stripe\Identity\VerificationReport::OBJECT_NAME => \EDD\Vendor\Stripe\Identity\VerificationReport::class, + \EDD\Vendor\Stripe\Identity\VerificationSession::OBJECT_NAME => \EDD\Vendor\Stripe\Identity\VerificationSession::class, + \EDD\Vendor\Stripe\Invoice::OBJECT_NAME => \EDD\Vendor\Stripe\Invoice::class, + \EDD\Vendor\Stripe\InvoiceItem::OBJECT_NAME => \EDD\Vendor\Stripe\InvoiceItem::class, + \EDD\Vendor\Stripe\InvoiceLineItem::OBJECT_NAME => \EDD\Vendor\Stripe\InvoiceLineItem::class, + \EDD\Vendor\Stripe\InvoiceRenderingTemplate::OBJECT_NAME => \EDD\Vendor\Stripe\InvoiceRenderingTemplate::class, + \EDD\Vendor\Stripe\Issuing\Authorization::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\Authorization::class, + \EDD\Vendor\Stripe\Issuing\Card::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\Card::class, + \EDD\Vendor\Stripe\Issuing\Cardholder::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\Cardholder::class, + \EDD\Vendor\Stripe\Issuing\Dispute::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\Dispute::class, + \EDD\Vendor\Stripe\Issuing\PersonalizationDesign::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\PersonalizationDesign::class, + \EDD\Vendor\Stripe\Issuing\PhysicalBundle::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\PhysicalBundle::class, + \EDD\Vendor\Stripe\Issuing\Token::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\Token::class, + \EDD\Vendor\Stripe\Issuing\Transaction::OBJECT_NAME => \EDD\Vendor\Stripe\Issuing\Transaction::class, + \EDD\Vendor\Stripe\LineItem::OBJECT_NAME => \EDD\Vendor\Stripe\LineItem::class, + \EDD\Vendor\Stripe\LoginLink::OBJECT_NAME => \EDD\Vendor\Stripe\LoginLink::class, + \EDD\Vendor\Stripe\Mandate::OBJECT_NAME => \EDD\Vendor\Stripe\Mandate::class, + \EDD\Vendor\Stripe\PaymentIntent::OBJECT_NAME => \EDD\Vendor\Stripe\PaymentIntent::class, + \EDD\Vendor\Stripe\PaymentLink::OBJECT_NAME => \EDD\Vendor\Stripe\PaymentLink::class, + \EDD\Vendor\Stripe\PaymentMethod::OBJECT_NAME => \EDD\Vendor\Stripe\PaymentMethod::class, + \EDD\Vendor\Stripe\PaymentMethodConfiguration::OBJECT_NAME => \EDD\Vendor\Stripe\PaymentMethodConfiguration::class, + \EDD\Vendor\Stripe\PaymentMethodDomain::OBJECT_NAME => \EDD\Vendor\Stripe\PaymentMethodDomain::class, + \EDD\Vendor\Stripe\Payout::OBJECT_NAME => \EDD\Vendor\Stripe\Payout::class, + \EDD\Vendor\Stripe\Person::OBJECT_NAME => \EDD\Vendor\Stripe\Person::class, + \EDD\Vendor\Stripe\Plan::OBJECT_NAME => \EDD\Vendor\Stripe\Plan::class, + \EDD\Vendor\Stripe\Price::OBJECT_NAME => \EDD\Vendor\Stripe\Price::class, + \EDD\Vendor\Stripe\Product::OBJECT_NAME => \EDD\Vendor\Stripe\Product::class, + \EDD\Vendor\Stripe\ProductFeature::OBJECT_NAME => \EDD\Vendor\Stripe\ProductFeature::class, + \EDD\Vendor\Stripe\PromotionCode::OBJECT_NAME => \EDD\Vendor\Stripe\PromotionCode::class, + \EDD\Vendor\Stripe\Quote::OBJECT_NAME => \EDD\Vendor\Stripe\Quote::class, + \EDD\Vendor\Stripe\Radar\EarlyFraudWarning::OBJECT_NAME => \EDD\Vendor\Stripe\Radar\EarlyFraudWarning::class, + \EDD\Vendor\Stripe\Radar\ValueList::OBJECT_NAME => \EDD\Vendor\Stripe\Radar\ValueList::class, + \EDD\Vendor\Stripe\Radar\ValueListItem::OBJECT_NAME => \EDD\Vendor\Stripe\Radar\ValueListItem::class, + \EDD\Vendor\Stripe\Refund::OBJECT_NAME => \EDD\Vendor\Stripe\Refund::class, + \EDD\Vendor\Stripe\Reporting\ReportRun::OBJECT_NAME => \EDD\Vendor\Stripe\Reporting\ReportRun::class, + \EDD\Vendor\Stripe\Reporting\ReportType::OBJECT_NAME => \EDD\Vendor\Stripe\Reporting\ReportType::class, + \EDD\Vendor\Stripe\ReserveTransaction::OBJECT_NAME => \EDD\Vendor\Stripe\ReserveTransaction::class, + \EDD\Vendor\Stripe\Review::OBJECT_NAME => \EDD\Vendor\Stripe\Review::class, + \EDD\Vendor\Stripe\SetupAttempt::OBJECT_NAME => \EDD\Vendor\Stripe\SetupAttempt::class, + \EDD\Vendor\Stripe\SetupIntent::OBJECT_NAME => \EDD\Vendor\Stripe\SetupIntent::class, + \EDD\Vendor\Stripe\ShippingRate::OBJECT_NAME => \EDD\Vendor\Stripe\ShippingRate::class, + \EDD\Vendor\Stripe\Sigma\ScheduledQueryRun::OBJECT_NAME => \EDD\Vendor\Stripe\Sigma\ScheduledQueryRun::class, + \EDD\Vendor\Stripe\Source::OBJECT_NAME => \EDD\Vendor\Stripe\Source::class, + \EDD\Vendor\Stripe\SourceMandateNotification::OBJECT_NAME => \EDD\Vendor\Stripe\SourceMandateNotification::class, + \EDD\Vendor\Stripe\SourceTransaction::OBJECT_NAME => \EDD\Vendor\Stripe\SourceTransaction::class, + \EDD\Vendor\Stripe\Subscription::OBJECT_NAME => \EDD\Vendor\Stripe\Subscription::class, + \EDD\Vendor\Stripe\SubscriptionItem::OBJECT_NAME => \EDD\Vendor\Stripe\SubscriptionItem::class, + \EDD\Vendor\Stripe\SubscriptionSchedule::OBJECT_NAME => \EDD\Vendor\Stripe\SubscriptionSchedule::class, + \EDD\Vendor\Stripe\Tax\Calculation::OBJECT_NAME => \EDD\Vendor\Stripe\Tax\Calculation::class, + \EDD\Vendor\Stripe\Tax\CalculationLineItem::OBJECT_NAME => \EDD\Vendor\Stripe\Tax\CalculationLineItem::class, + \EDD\Vendor\Stripe\Tax\Registration::OBJECT_NAME => \EDD\Vendor\Stripe\Tax\Registration::class, + \EDD\Vendor\Stripe\Tax\Settings::OBJECT_NAME => \EDD\Vendor\Stripe\Tax\Settings::class, + \EDD\Vendor\Stripe\Tax\Transaction::OBJECT_NAME => \EDD\Vendor\Stripe\Tax\Transaction::class, + \EDD\Vendor\Stripe\Tax\TransactionLineItem::OBJECT_NAME => \EDD\Vendor\Stripe\Tax\TransactionLineItem::class, + \EDD\Vendor\Stripe\TaxCode::OBJECT_NAME => \EDD\Vendor\Stripe\TaxCode::class, + \EDD\Vendor\Stripe\TaxDeductedAtSource::OBJECT_NAME => \EDD\Vendor\Stripe\TaxDeductedAtSource::class, + \EDD\Vendor\Stripe\TaxId::OBJECT_NAME => \EDD\Vendor\Stripe\TaxId::class, + \EDD\Vendor\Stripe\TaxRate::OBJECT_NAME => \EDD\Vendor\Stripe\TaxRate::class, + \EDD\Vendor\Stripe\Terminal\Configuration::OBJECT_NAME => \EDD\Vendor\Stripe\Terminal\Configuration::class, + \EDD\Vendor\Stripe\Terminal\ConnectionToken::OBJECT_NAME => \EDD\Vendor\Stripe\Terminal\ConnectionToken::class, + \EDD\Vendor\Stripe\Terminal\Location::OBJECT_NAME => \EDD\Vendor\Stripe\Terminal\Location::class, + \EDD\Vendor\Stripe\Terminal\Reader::OBJECT_NAME => \EDD\Vendor\Stripe\Terminal\Reader::class, + \EDD\Vendor\Stripe\TestHelpers\TestClock::OBJECT_NAME => \EDD\Vendor\Stripe\TestHelpers\TestClock::class, + \EDD\Vendor\Stripe\Token::OBJECT_NAME => \EDD\Vendor\Stripe\Token::class, + \EDD\Vendor\Stripe\Topup::OBJECT_NAME => \EDD\Vendor\Stripe\Topup::class, + \EDD\Vendor\Stripe\Transfer::OBJECT_NAME => \EDD\Vendor\Stripe\Transfer::class, + \EDD\Vendor\Stripe\TransferReversal::OBJECT_NAME => \EDD\Vendor\Stripe\TransferReversal::class, + \EDD\Vendor\Stripe\Treasury\CreditReversal::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\CreditReversal::class, + \EDD\Vendor\Stripe\Treasury\DebitReversal::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\DebitReversal::class, + \EDD\Vendor\Stripe\Treasury\FinancialAccount::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\FinancialAccount::class, + \EDD\Vendor\Stripe\Treasury\FinancialAccountFeatures::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\FinancialAccountFeatures::class, + \EDD\Vendor\Stripe\Treasury\InboundTransfer::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\InboundTransfer::class, + \EDD\Vendor\Stripe\Treasury\OutboundPayment::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\OutboundPayment::class, + \EDD\Vendor\Stripe\Treasury\OutboundTransfer::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\OutboundTransfer::class, + \EDD\Vendor\Stripe\Treasury\ReceivedCredit::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\ReceivedCredit::class, + \EDD\Vendor\Stripe\Treasury\ReceivedDebit::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\ReceivedDebit::class, + \EDD\Vendor\Stripe\Treasury\Transaction::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\Transaction::class, + \EDD\Vendor\Stripe\Treasury\TransactionEntry::OBJECT_NAME => \EDD\Vendor\Stripe\Treasury\TransactionEntry::class, + \EDD\Vendor\Stripe\UsageRecord::OBJECT_NAME => \EDD\Vendor\Stripe\UsageRecord::class, + \EDD\Vendor\Stripe\UsageRecordSummary::OBJECT_NAME => \EDD\Vendor\Stripe\UsageRecordSummary::class, + \EDD\Vendor\Stripe\WebhookEndpoint::OBJECT_NAME => \EDD\Vendor\Stripe\WebhookEndpoint::class, + // object classes: The end of the section generated from our OpenAPI spec + ]; +} diff --git a/libraries/Stripe/lib/Util/RandomGenerator.php b/libraries/Stripe/lib/Util/RandomGenerator.php new file mode 100644 index 00000000000..ee041ea278d --- /dev/null +++ b/libraries/Stripe/lib/Util/RandomGenerator.php @@ -0,0 +1,36 @@ + a list of headers that should be persisted across requests + */ + public static $HEADERS_TO_PERSIST = [ + 'Stripe-Account', + 'Stripe-Version', + ]; + + /** @var array */ + public $headers; + + /** @var null|string */ + public $apiKey; + + /** @var null|string */ + public $apiBase; + + /** + * @param null|string $key + * @param array $headers + * @param null|string $base + */ + public function __construct($key = null, $headers = [], $base = null) + { + $this->apiKey = $key; + $this->headers = $headers; + $this->apiBase = $base; + } + + /** + * @return array + */ + public function __debugInfo() + { + return [ + 'apiKey' => $this->redactedApiKey(), + 'headers' => $this->headers, + 'apiBase' => $this->apiBase, + ]; + } + + /** + * Unpacks an options array and merges it into the existing RequestOptions + * object. + * + * @param null|array|RequestOptions|string $options a key => value array + * @param bool $strict when true, forbid string form and arbitrary keys in array form + * + * @return RequestOptions + */ + public function merge($options, $strict = false) + { + $other_options = self::parse($options, $strict); + if (null === $other_options->apiKey) { + $other_options->apiKey = $this->apiKey; + } + if (null === $other_options->apiBase) { + $other_options->apiBase = $this->apiBase; + } + $other_options->headers = \array_merge($this->headers, $other_options->headers); + + return $other_options; + } + + /** + * Discards all headers that we don't want to persist across requests. + */ + public function discardNonPersistentHeaders() + { + foreach ($this->headers as $k => $v) { + if (!\in_array($k, self::$HEADERS_TO_PERSIST, true)) { + unset($this->headers[$k]); + } + } + } + + /** + * Unpacks an options array into an RequestOptions object. + * + * @param null|array|RequestOptions|string $options a key => value array + * @param bool $strict when true, forbid string form and arbitrary keys in array form + * + * @throws \EDD\Vendor\Stripe\Exception\InvalidArgumentException + * + * @return RequestOptions + */ + public static function parse($options, $strict = false) + { + if ($options instanceof self) { + return clone $options; + } + + if (null === $options) { + return new RequestOptions(null, [], null); + } + + if (\is_string($options)) { + if ($strict) { + $message = 'Do not pass a string for request options. If you want to set the ' + . 'API key, pass an array like ["api_key" => ] instead.'; + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException($message); + } + + return new RequestOptions($options, [], null); + } + + if (\is_array($options)) { + $headers = []; + $key = null; + $base = null; + + if (\array_key_exists('api_key', $options)) { + $key = $options['api_key']; + unset($options['api_key']); + } + if (\array_key_exists('idempotency_key', $options)) { + $headers['Idempotency-Key'] = $options['idempotency_key']; + unset($options['idempotency_key']); + } + if (\array_key_exists('stripe_account', $options)) { + $headers['Stripe-Account'] = $options['stripe_account']; + unset($options['stripe_account']); + } + if (\array_key_exists('stripe_version', $options)) { + $headers['Stripe-Version'] = $options['stripe_version']; + unset($options['stripe_version']); + } + if (\array_key_exists('api_base', $options)) { + $base = $options['api_base']; + unset($options['api_base']); + } + + if ($strict && !empty($options)) { + $message = 'Got unexpected keys in options array: ' . \implode(', ', \array_keys($options)); + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException($message); + } + + return new RequestOptions($key, $headers, $base); + } + + $message = 'The second argument to EDD\Vendor\Stripe API method calls is an ' + . 'optional per-request apiKey, which must be a string, or ' + . 'per-request options, which must be an array. (HINT: you can set ' + . 'a global apiKey by "Stripe::setApiKey()")'; + + throw new \EDD\Vendor\Stripe\Exception\InvalidArgumentException($message); + } + + /** @return string */ + private function redactedApiKey() + { + if (null === $this->apiKey) { + return ''; + } + + $pieces = \explode('_', $this->apiKey, 3); + $last = \array_pop($pieces); + $redactedLast = \strlen($last) > 4 + ? (\str_repeat('*', \strlen($last) - 4) . \substr($last, -4)) + : $last; + $pieces[] = $redactedLast; + + return \implode('_', $pieces); + } +} diff --git a/libraries/Stripe/lib/Util/Set.php b/libraries/Stripe/lib/Util/Set.php new file mode 100644 index 00000000000..793ff7ece3f --- /dev/null +++ b/libraries/Stripe/lib/Util/Set.php @@ -0,0 +1,48 @@ +_elts = []; + foreach ($members as $item) { + $this->_elts[$item] = true; + } + } + + public function includes($elt) + { + return isset($this->_elts[$elt]); + } + + public function add($elt) + { + $this->_elts[$elt] = true; + } + + public function discard($elt) + { + unset($this->_elts[$elt]); + } + + public function toArray() + { + return \array_keys($this->_elts); + } + + /** + * @return ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->toArray()); + } +} diff --git a/libraries/Stripe/lib/Util/Util.php b/libraries/Stripe/lib/Util/Util.php new file mode 100644 index 00000000000..c1e85d0cd6d --- /dev/null +++ b/libraries/Stripe/lib/Util/Util.php @@ -0,0 +1,269 @@ +id; + } + if (static::isList($h)) { + $results = []; + foreach ($h as $v) { + $results[] = static::objectsToIds($v); + } + + return $results; + } + if (\is_array($h)) { + $results = []; + foreach ($h as $k => $v) { + if (null === $v) { + continue; + } + $results[$k] = static::objectsToIds($v); + } + + return $results; + } + + return $h; + } + + /** + * @param array $params + * + * @return string + */ + public static function encodeParameters($params) + { + $flattenedParams = self::flattenParams($params); + $pieces = []; + foreach ($flattenedParams as $param) { + list($k, $v) = $param; + $pieces[] = self::urlEncode($k) . '=' . self::urlEncode($v); + } + + return \implode('&', $pieces); + } + + /** + * @param array $params + * @param null|string $parentKey + * + * @return array + */ + public static function flattenParams($params, $parentKey = null) + { + $result = []; + + foreach ($params as $key => $value) { + $calculatedKey = $parentKey ? "{$parentKey}[{$key}]" : $key; + + if (self::isList($value)) { + $result = \array_merge($result, self::flattenParamsList($value, $calculatedKey)); + } elseif (\is_array($value)) { + $result = \array_merge($result, self::flattenParams($value, $calculatedKey)); + } else { + \array_push($result, [$calculatedKey, $value]); + } + } + + return $result; + } + + /** + * @param array $value + * @param string $calculatedKey + * + * @return array + */ + public static function flattenParamsList($value, $calculatedKey) + { + $result = []; + + foreach ($value as $i => $elem) { + if (self::isList($elem)) { + $result = \array_merge($result, self::flattenParamsList($elem, $calculatedKey)); + } elseif (\is_array($elem)) { + $result = \array_merge($result, self::flattenParams($elem, "{$calculatedKey}[{$i}]")); + } else { + \array_push($result, ["{$calculatedKey}[{$i}]", $elem]); + } + } + + return $result; + } + + /** + * @param string $key a string to URL-encode + * + * @return string the URL-encoded string + */ + public static function urlEncode($key) + { + $s = \urlencode((string) $key); + + // Don't use strict form encoding by changing the square bracket control + // characters back to their literals. This is fine by the server, and + // makes these parameter strings easier to read. + $s = \str_replace('%5B', '[', $s); + + return \str_replace('%5D', ']', $s); + } + + public static function normalizeId($id) + { + if (\is_array($id)) { + // see https://github.com/stripe/stripe-php/pull/1602 + if (!isset($id['id'])) { + return [null, $id]; + } + $params = $id; + $id = $params['id']; + unset($params['id']); + } else { + $params = []; + } + + return [$id, $params]; + } + + /** + * Returns UNIX timestamp in milliseconds. + * + * @return int current time in millis + */ + public static function currentTimeMillis() + { + return (int) \round(\microtime(true) * 1000); + } +} diff --git a/libraries/Stripe/lib/Webhook.php b/libraries/Stripe/lib/Webhook.php new file mode 100644 index 00000000000..82267958063 --- /dev/null +++ b/libraries/Stripe/lib/Webhook.php @@ -0,0 +1,42 @@ +webhook endpoints via the API to be + * notified about events that happen in your EDD\Vendor\Stripe account or connected + * accounts. + * + * Most users configure webhooks from the dashboard, which provides a user interface for registering and testing your webhook endpoints. + * + * Related guide: Setting up webhooks + * + * @property string $id Unique identifier for the object. + * @property string $object String representing the object's type. Objects of the same type share the same value. + * @property null|string $api_version The API version events are rendered as for this webhook endpoint. + * @property null|string $application The ID of the associated Connect application. + * @property int $created Time at which the object was created. Measured in seconds since the Unix epoch. + * @property null|string $description An optional description of what the webhook is used for. + * @property string[] $enabled_events The list of events to enable for this endpoint. ['*'] indicates that all events are enabled, except those that require explicit selection. + * @property bool $livemode Has the value true if the object exists in live mode or the value false if the object exists in test mode. + * @property \EDD\Vendor\Stripe\StripeObject $metadata Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + * @property null|string $secret The endpoint's secret, used to generate webhook signatures. Only returned at creation. + * @property string $status The status of the webhook. It can be enabled or disabled. + * @property string $url The URL of the webhook endpoint. + */ +class WebhookEndpoint extends ApiResource +{ + const OBJECT_NAME = 'webhook_endpoint'; + + use ApiOperations\Update; + + /** + * A webhook endpoint must have a url and a list of + * enabled_events. You may optionally specify the Boolean + * connect parameter. If set to true, then a Connect webhook endpoint + * that notifies the specified url about events from all connected + * accounts is created; otherwise an account webhook endpoint that notifies the + * specified url only about events from your account is created. You + * can also create webhook endpoints in the webhooks settings + * section of the Dashboard. + * + * @param null|array $params + * @param null|array|string $options + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\WebhookEndpoint the created resource + */ + public static function create($params = null, $options = null) + { + self::_validateParams($params); + $url = static::classUrl(); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $options); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } + + /** + * You can also delete webhook endpoints via the webhook endpoint + * management page of the EDD\Vendor\Stripe dashboard. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\WebhookEndpoint the deleted resource + */ + public function delete($params = null, $opts = null) + { + self::_validateParams($params); + + $url = $this->instanceUrl(); + list($response, $opts) = $this->_request('delete', $url, $params, $opts); + $this->refreshFrom($response, $opts); + + return $this; + } + + /** + * Returns a list of your webhook endpoints. + * + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\Collection<\EDD\Vendor\Stripe\WebhookEndpoint> of ApiResources + */ + public static function all($params = null, $opts = null) + { + $url = static::classUrl(); + + return static::_requestPage($url, \EDD\Vendor\Stripe\Collection::class, $params, $opts); + } + + /** + * Retrieves the webhook endpoint with the given ID. + * + * @param array|string $id the ID of the API resource to retrieve, or an options array containing an `id` key + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\WebhookEndpoint + */ + public static function retrieve($id, $opts = null) + { + $opts = \EDD\Vendor\Stripe\Util\RequestOptions::parse($opts); + $instance = new static($id, $opts); + $instance->refresh(); + + return $instance; + } + + /** + * Updates the webhook endpoint. You may edit the url, the list of + * enabled_events, and the status of your endpoint. + * + * @param string $id the ID of the resource to update + * @param null|array $params + * @param null|array|string $opts + * + * @throws \EDD\Vendor\Stripe\Exception\ApiErrorException if the request fails + * + * @return \EDD\Vendor\Stripe\WebhookEndpoint the updated resource + */ + public static function update($id, $params = null, $opts = null) + { + self::_validateParams($params); + $url = static::resourceUrl($id); + + list($response, $opts) = static::_staticRequest('post', $url, $params, $opts); + $obj = \EDD\Vendor\Stripe\Util\Util::convertToStripeObject($response->json, $opts); + $obj->setLastResponse($response); + + return $obj; + } +} diff --git a/libraries/Stripe/lib/WebhookSignature.php b/libraries/Stripe/lib/WebhookSignature.php new file mode 100644 index 00000000000..f7a8ddf3004 --- /dev/null +++ b/libraries/Stripe/lib/WebhookSignature.php @@ -0,0 +1,140 @@ + 0) && (\abs(\time() - $timestamp) > $tolerance)) { + throw Exception\SignatureVerificationException::factory( + 'Timestamp outside the tolerance zone', + $payload, + $header + ); + } + + return true; + } + + /** + * Extracts the timestamp in a signature header. + * + * @param string $header the signature header + * + * @return int the timestamp contained in the header, or -1 if no valid + * timestamp is found + */ + private static function getTimestamp($header) + { + $items = \explode(',', $header); + + foreach ($items as $item) { + $itemParts = \explode('=', $item, 2); + if ('t' === $itemParts[0]) { + if (!\is_numeric($itemParts[1])) { + return -1; + } + + return (int) ($itemParts[1]); + } + } + + return -1; + } + + /** + * Extracts the signatures matching a given scheme in a signature header. + * + * @param string $header the signature header + * @param string $scheme the signature scheme to look for + * + * @return array the list of signatures matching the provided scheme + */ + private static function getSignatures($header, $scheme) + { + $signatures = []; + $items = \explode(',', $header); + + foreach ($items as $item) { + $itemParts = \explode('=', $item, 2); + if (\trim($itemParts[0]) === $scheme) { + $signatures[] = $itemParts[1]; + } + } + + return $signatures; + } + + /** + * Computes the signature for a given payload and secret. + * + * The current scheme used by EDD\Vendor\Stripe ("v1") is HMAC/SHA-256. + * + * @param string $payload the payload to sign + * @param string $secret the secret used to generate the signature + * + * @return string the signature as a string + */ + private static function computeSignature($payload, $secret) + { + return \hash_hmac('sha256', $payload, $secret); + } +} diff --git a/libraries/Symfony/Component/Translation/CHANGELOG.md b/libraries/Symfony/Component/Translation/CHANGELOG.md new file mode 100644 index 00000000000..5f9098c07a0 --- /dev/null +++ b/libraries/Symfony/Component/Translation/CHANGELOG.md @@ -0,0 +1,205 @@ +CHANGELOG +========= + +6.4 +--- + + * Give current locale to `LocaleSwitcher::runWithLocale()`'s callback + * Add `--as-tree` option to `translation:pull` command to write YAML messages as a tree-like structure + * [BC BREAK] Add argument `$buildDir` to `DataCollectorTranslator::warmUp()` + * Add `DataCollectorTranslatorPass` and `LoggingTranslatorPass` (moved from `FrameworkBundle`) + * Add `PhraseTranslationProvider` + +6.2.7 +----- + + * [BC BREAK] The following data providers for `ProviderFactoryTestCase` are now static: + `supportsProvider()`, `createProvider()`, `unsupportedSchemeProvider()`and `incompleteDsnProvider()` + * [BC BREAK] `ProviderTestCase::toStringProvider()` is now static + +6.2 +--- + + * Deprecate `PhpStringTokenParser` + * Deprecate `PhpExtractor` in favor of `PhpAstExtractor` + * Add `PhpAstExtractor` (requires [nikic/php-parser](https://github.com/nikic/php-parser) to be installed) + +6.1 +--- + + * Parameters implementing `TranslatableInterface` are processed + * Add the file extension to the `XliffFileDumper` constructor + +5.4 +--- + + * Add `github` format & autodetection to render errors as annotations when + running the XLIFF linter command in a Github Actions environment. + * Translation providers are not experimental anymore + +5.3 +--- + + * Add `translation:pull` and `translation:push` commands to manage translations with third-party providers + * Add `TranslatorBagInterface::getCatalogues` method + * Add support to load XLIFF string in `XliffFileLoader` + +5.2.0 +----- + + * added support for calling `trans` with ICU formatted messages + * added `PseudoLocalizationTranslator` + * added `TranslatableMessage` objects that represent a message that can be translated + * added the `t()` function to easily create `TranslatableMessage` objects + * Added support for extracting messages from `TranslatableMessage` objects + +5.1.0 +----- + + * added support for `name` attribute on `unit` element from xliff2 to be used as a translation key instead of always the `source` element + +5.0.0 +----- + + * removed support for using `null` as the locale in `Translator` + * removed `TranslatorInterface` + * removed `MessageSelector` + * removed `ChoiceMessageFormatterInterface` + * removed `PluralizationRule` + * removed `Interval` + * removed `transChoice()` methods, use the trans() method instead with a %count% parameter + * removed `FileDumper::setBackup()` and `TranslationWriter::disableBackup()` + * removed `MessageFormatter::choiceFormat()` + * added argument `$filename` to `PhpExtractor::parseTokens()` + * removed support for implicit STDIN usage in the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * deprecated support for using `null` as the locale in `Translator` + * deprecated accepting STDIN implicitly when using the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit. + * Marked the `TranslationDataCollector` class as `@final`. + +4.3.0 +----- + + * Improved Xliff 1.2 loader to load the original file's metadata + * Added `TranslatorPathsPass` + +4.2.0 +----- + + * Started using ICU parent locales as fallback locales. + * allow using the ICU message format using domains with the "+intl-icu" suffix + * deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter + * deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface` + * deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead + * Added `IntlFormatter` and `IntlFormatterInterface` + * added support for multiple files and directories in `XliffLintCommand` + * Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal + +4.1.0 +----- + + * The `FileDumper::setBackup()` method is deprecated. + * The `TranslationWriter::disableBackup()` method is deprecated. + * The `XliffFileDumper` will write "name" on the "unit" node when dumping XLIFF 2.0. + +4.0.0 +----- + + * removed the backup feature of the `FileDumper` class + * removed `TranslationWriter::writeTranslations()` method + * removed support for passing `MessageSelector` instances to the constructor of the `Translator` class + +3.4.0 +----- + + * Added `TranslationDumperPass` + * Added `TranslationExtractorPass` + * Added `TranslatorPass` + * Added `TranslationReader` and `TranslationReaderInterface` + * Added `` section to the Xliff 2.0 dumper. + * Improved Xliff 2.0 loader to load `` section. + * Added `TranslationWriterInterface` + * Deprecated `TranslationWriter::writeTranslations` in favor of `TranslationWriter::write` + * added support for adding custom message formatter and decoupling the default one. + * Added `PhpExtractor` + * Added `PhpStringTokenParser` + +3.2.0 +----- + + * Added support for escaping `|` in plural translations with double pipe. + +3.1.0 +----- + + * Deprecated the backup feature of the file dumper classes. + +3.0.0 +----- + + * removed `FileDumper::format()` method. + * Changed the visibility of the locale property in `Translator` from protected to private. + +2.8.0 +----- + + * deprecated FileDumper::format(), overwrite FileDumper::formatCatalogue() instead. + * deprecated Translator::getMessages(), rely on TranslatorBagInterface::getCatalogue() instead. + * added `FileDumper::formatCatalogue` which allows format the catalogue without dumping it into file. + * added option `json_encoding` to JsonFileDumper + * added options `as_tree`, `inline` to YamlFileDumper + * added support for XLIFF 2.0. + * added support for XLIFF target and tool attributes. + * added message parameters to DataCollectorTranslator. + * [DEPRECATION] The `DiffOperation` class has been deprecated and + will be removed in Symfony 3.0, since its operation has nothing to do with 'diff', + so the class name is misleading. The `TargetOperation` class should be used for + this use-case instead. + +2.7.0 +----- + + * added DataCollectorTranslator for collecting the translated messages. + +2.6.0 +----- + + * added possibility to cache catalogues + * added TranslatorBagInterface + * added LoggingTranslator + * added Translator::getMessages() for retrieving the message catalogue as an array + +2.5.0 +----- + + * added relative file path template to the file dumpers + * added optional backup to the file dumpers + * changed IcuResFileDumper to extend FileDumper + +2.3.0 +----- + + * added classes to make operations on catalogues (like making a diff or a merge on 2 catalogues) + * added Translator::getFallbackLocales() + * deprecated Translator::setFallbackLocale() in favor of the new Translator::setFallbackLocales() method + +2.2.0 +----- + + * QtTranslationsLoader class renamed to QtFileLoader. QtTranslationsLoader is deprecated and will be removed in 2.3. + * [BC BREAK] uniformized the exception thrown by the load() method when an error occurs. The load() method now + throws Symfony\Component\Translation\Exception\NotFoundResourceException when a resource cannot be found + and Symfony\Component\Translation\Exception\InvalidResourceException when a resource is invalid. + * changed the exception class thrown by some load() methods from \RuntimeException to \InvalidArgumentException + (IcuDatFileLoader, IcuResFileLoader and QtFileLoader) + +2.1.0 +----- + + * added support for more than one fallback locale + * added support for extracting translation messages from templates (Twig and PHP) + * added dumpers for translation catalogs + * added support for QT, gettext, and ResourceBundles diff --git a/libraries/Symfony/Component/Translation/Catalogue/AbstractOperation.php b/libraries/Symfony/Component/Translation/Catalogue/AbstractOperation.php new file mode 100644 index 00000000000..5a8ed9c9a72 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Catalogue/AbstractOperation.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Catalogue; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\Exception\LogicException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Base catalogues binary operation class. + * + * A catalogue binary operation performs operation on + * source (the left argument) and target (the right argument) catalogues. + * + * @author Jean-François Simon + */ +abstract class AbstractOperation implements OperationInterface +{ + public const OBSOLETE_BATCH = 'obsolete'; + public const NEW_BATCH = 'new'; + public const ALL_BATCH = 'all'; + + protected $source; + protected $target; + protected $result; + + /** + * This array stores 'all', 'new' and 'obsolete' messages for all valid domains. + * + * The data structure of this array is as follows: + * + * [ + * 'domain 1' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * 'domain 2' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * ... + * ] + * + * @var array The array that stores 'all', 'new' and 'obsolete' messages + */ + protected $messages; + + private array $domains; + + /** + * @throws LogicException + */ + public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + if ($source->getLocale() !== $target->getLocale()) { + throw new LogicException('Operated catalogues must belong to the same locale.'); + } + + $this->source = $source; + $this->target = $target; + $this->result = new MessageCatalogue($source->getLocale()); + $this->messages = []; + } + + public function getDomains(): array + { + if (!isset($this->domains)) { + $domains = []; + foreach ([$this->source, $this->target] as $catalogue) { + foreach ($catalogue->getDomains() as $domain) { + $domains[$domain] = $domain; + + if ($catalogue->all($domainIcu = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX)) { + $domains[$domainIcu] = $domainIcu; + } + } + } + + $this->domains = array_values($domains); + } + + return $this->domains; + } + + public function getMessages(string $domain): array + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::ALL_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::ALL_BATCH]; + } + + public function getNewMessages(string $domain): array + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::NEW_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::NEW_BATCH]; + } + + public function getObsoleteMessages(string $domain): array + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::OBSOLETE_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::OBSOLETE_BATCH]; + } + + public function getResult(): MessageCatalogueInterface + { + foreach ($this->getDomains() as $domain) { + if (!isset($this->messages[$domain])) { + $this->processDomain($domain); + } + } + + return $this->result; + } + + /** + * @param self::*_BATCH $batch + */ + public function moveMessagesToIntlDomainsIfPossible(string $batch = self::ALL_BATCH): void + { + // If MessageFormatter class does not exists, intl domains are not supported. + if (!class_exists(\MessageFormatter::class)) { + return; + } + + foreach ($this->getDomains() as $domain) { + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + $messages = match ($batch) { + self::OBSOLETE_BATCH => $this->getObsoleteMessages($domain), + self::NEW_BATCH => $this->getNewMessages($domain), + self::ALL_BATCH => $this->getMessages($domain), + default => throw new \InvalidArgumentException(sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH)), + }; + + if (!$messages || (!$this->source->all($intlDomain) && $this->source->all($domain))) { + continue; + } + + $result = $this->getResult(); + $allIntlMessages = $result->all($intlDomain); + $currentMessages = array_diff_key($messages, $result->all($domain)); + $result->replace($currentMessages, $domain); + $result->replace($allIntlMessages + $messages, $intlDomain); + } + } + + /** + * Performs operation on source and target catalogues for the given domain and + * stores the results. + * + * @param string $domain The domain which the operation will be performed for + * + * @return void + */ + abstract protected function processDomain(string $domain); +} diff --git a/libraries/Symfony/Component/Translation/Catalogue/MergeOperation.php b/libraries/Symfony/Component/Translation/Catalogue/MergeOperation.php new file mode 100644 index 00000000000..e9871920444 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Catalogue/MergeOperation.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Catalogue; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Merge operation between two catalogues as follows: + * all = source ∪ target = {x: x ∈ source ∨ x ∈ target} + * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} + * obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ source ∧ x ∉ target} = ∅ + * Basically, the result contains messages from both catalogues. + * + * @author Jean-François Simon + */ +class MergeOperation extends AbstractOperation +{ + /** + * @return void + */ + protected function processDomain(string $domain) + { + $this->messages[$domain] = [ + 'all' => [], + 'new' => [], + 'obsolete' => [], + ]; + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + + foreach ($this->target->getCatalogueMetadata('', $domain) ?? [] as $key => $value) { + if (null === $this->result->getCatalogueMetadata($key, $domain)) { + $this->result->setCatalogueMetadata($key, $value, $domain); + } + } + + foreach ($this->target->getCatalogueMetadata('', $intlDomain) ?? [] as $key => $value) { + if (null === $this->result->getCatalogueMetadata($key, $intlDomain)) { + $this->result->setCatalogueMetadata($key, $value, $intlDomain); + } + } + + foreach ($this->source->all($domain) as $id => $message) { + $this->messages[$domain]['all'][$id] = $message; + $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + } + } +} diff --git a/libraries/Symfony/Component/Translation/Catalogue/OperationInterface.php b/libraries/Symfony/Component/Translation/Catalogue/OperationInterface.php new file mode 100644 index 00000000000..7b7529e96cf --- /dev/null +++ b/libraries/Symfony/Component/Translation/Catalogue/OperationInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Catalogue; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Represents an operation on catalogue(s). + * + * An instance of this interface performs an operation on one or more catalogues and + * stores intermediate and final results of the operation. + * + * The first catalogue in its argument(s) is called the 'source catalogue' or 'source' and + * the following results are stored: + * + * Messages: also called 'all', are valid messages for the given domain after the operation is performed. + * + * New Messages: also called 'new' (new = all ∖ source = {x: x ∈ all ∧ x ∉ source}). + * + * Obsolete Messages: also called 'obsolete' (obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ all}). + * + * Result: also called 'result', is the resulting catalogue for the given domain that holds the same messages as 'all'. + * + * @author Jean-François Simon + */ +interface OperationInterface +{ + /** + * Returns domains affected by operation. + */ + public function getDomains(): array; + + /** + * Returns all valid messages ('all') after operation. + */ + public function getMessages(string $domain): array; + + /** + * Returns new messages ('new') after operation. + */ + public function getNewMessages(string $domain): array; + + /** + * Returns obsolete messages ('obsolete') after operation. + */ + public function getObsoleteMessages(string $domain): array; + + /** + * Returns resulting catalogue ('result'). + */ + public function getResult(): MessageCatalogueInterface; +} diff --git a/libraries/Symfony/Component/Translation/Catalogue/TargetOperation.php b/libraries/Symfony/Component/Translation/Catalogue/TargetOperation.php new file mode 100644 index 00000000000..a5450267b85 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Catalogue/TargetOperation.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Catalogue; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Target operation between two catalogues: + * intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target} + * all = intersection ∪ (target ∖ intersection) = target + * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} + * obsolete = source ∖ all = source ∖ target = {x: x ∈ source ∧ x ∉ target} + * Basically, the result contains messages from the target catalogue. + * + * @author Michael Lee + */ +class TargetOperation extends AbstractOperation +{ + /** + * @return void + */ + protected function processDomain(string $domain) + { + $this->messages[$domain] = [ + 'all' => [], + 'new' => [], + 'obsolete' => [], + ]; + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + + foreach ($this->target->getCatalogueMetadata('', $domain) ?? [] as $key => $value) { + if (null === $this->result->getCatalogueMetadata($key, $domain)) { + $this->result->setCatalogueMetadata($key, $value, $domain); + } + } + + foreach ($this->target->getCatalogueMetadata('', $intlDomain) ?? [] as $key => $value) { + if (null === $this->result->getCatalogueMetadata($key, $intlDomain)) { + $this->result->setCatalogueMetadata($key, $value, $intlDomain); + } + } + + // For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``, + // because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} + // + // For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));`` + // because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback} + // + // For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))`` + // because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} + + foreach ($this->source->all($domain) as $id => $message) { + if ($this->target->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } else { + $this->messages[$domain]['obsolete'][$id] = $message; + } + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + } + } +} diff --git a/libraries/Symfony/Component/Translation/CatalogueMetadataAwareInterface.php b/libraries/Symfony/Component/Translation/CatalogueMetadataAwareInterface.php new file mode 100644 index 00000000000..cf67453cbeb --- /dev/null +++ b/libraries/Symfony/Component/Translation/CatalogueMetadataAwareInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +/** + * This interface is used to get, set, and delete metadata about the Catalogue. + * + * @author Hugo Alliaume + */ +interface CatalogueMetadataAwareInterface +{ + /** + * Gets catalogue metadata for the given domain and key. + * + * Passing an empty domain will return an array with all catalogue metadata indexed by + * domain and then by key. Passing an empty key will return an array with all + * catalogue metadata for the given domain. + * + * @return mixed The value that was set or an array with the domains/keys or null + */ + public function getCatalogueMetadata(string $key = '', string $domain = 'messages'): mixed; + + /** + * Adds catalogue metadata to a message domain. + * + * @return void + */ + public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages'); + + /** + * Deletes catalogue metadata for the given key and domain. + * + * Passing an empty domain will delete all catalogue metadata. Passing an empty key will + * delete all metadata for the given domain. + * + * @return void + */ + public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages'); +} diff --git a/libraries/Symfony/Component/Translation/Command/TranslationPullCommand.php b/libraries/Symfony/Component/Translation/Command/TranslationPullCommand.php new file mode 100644 index 00000000000..d37483caeeb --- /dev/null +++ b/libraries/Symfony/Component/Translation/Command/TranslationPullCommand.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use EDD\Vendor\Symfony\Component\Translation\Catalogue\TargetOperation; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; +use EDD\Vendor\Symfony\Component\Translation\Provider\TranslationProviderCollection; +use EDD\Vendor\Symfony\Component\Translation\Reader\TranslationReaderInterface; +use EDD\Vendor\Symfony\Component\Translation\Writer\TranslationWriterInterface; + +/** + * @author Mathieu Santostefano + */ +#[AsCommand(name: 'translation:pull', description: 'Pull translations from a given provider.')] +final class TranslationPullCommand extends Command +{ + use TranslationTrait; + + private TranslationProviderCollection $providerCollection; + private TranslationWriterInterface $writer; + private TranslationReaderInterface $reader; + private string $defaultLocale; + private array $transPaths; + private array $enabledLocales; + + public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = []) + { + $this->providerCollection = $providerCollection; + $this->writer = $writer; + $this->reader = $reader; + $this->defaultLocale = $defaultLocale; + $this->transPaths = $transPaths; + $this->enabledLocales = $enabledLocales; + + parent::__construct(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('provider')) { + $suggestions->suggestValues($this->providerCollection->keys()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domains')) { + $provider = $this->providerCollection->get($input->getArgument('provider')); + + if (method_exists($provider, 'getDomains')) { + $suggestions->suggestValues($provider->getDomains()); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('locales')) { + $suggestions->suggestValues($this->enabledLocales); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['php', 'xlf', 'xlf12', 'xlf20', 'po', 'mo', 'yml', 'yaml', 'ts', 'csv', 'json', 'ini', 'res']); + } + } + + protected function configure(): void + { + $keys = $this->providerCollection->keys(); + $defaultProvider = 1 === \count($keys) ? $keys[0] : null; + + $this + ->setDefinition([ + new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider), + new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'), + new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'), + new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'), + new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'), + new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'), + new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Write messages as a tree-like structure. Needs --format=yaml. The given value defines the level where to switch to inline YAML'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command pulls translations from the given provider. Only +new translations are pulled, existing ones are not overwritten. + +You can overwrite existing translations (and remove the missing ones on local side) by using the --force flag: + + php %command.full_name% --force provider + +Full example: + + php %command.full_name% provider --force --domains=messages --domains=validators --locales=en + +This command pulls all translations associated with the messages and validators domains for the en locale. +Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case. +Local translations for others domains and locales are ignored. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $provider = $this->providerCollection->get($input->getArgument('provider')); + $force = $input->getOption('force'); + $intlIcu = $input->getOption('intl-icu'); + $locales = $input->getOption('locales') ?: $this->enabledLocales; + $domains = $input->getOption('domains'); + $format = $input->getOption('format'); + $asTree = (int) $input->getOption('as-tree'); + $xliffVersion = '1.2'; + + if ($intlIcu && !$force) { + $io->note('--intl-icu option only has an effect when used with --force. Here, it will be ignored.'); + } + + switch ($format) { + case 'xlf20': $xliffVersion = '2.0'; + // no break + case 'xlf12': $format = 'xlf'; + } + + $writeOptions = [ + 'path' => end($this->transPaths), + 'xliff_version' => $xliffVersion, + 'default_locale' => $this->defaultLocale, + 'as_tree' => (bool) $asTree, + 'inline' => $asTree, + ]; + + if (!$domains) { + $domains = $provider->getDomains(); + } + + $providerTranslations = $provider->read($domains, $locales); + + if ($force) { + foreach ($providerTranslations->getCatalogues() as $catalogue) { + $operation = new TargetOperation(new MessageCatalogue($catalogue->getLocale()), $catalogue); + if ($intlIcu) { + $operation->moveMessagesToIntlDomainsIfPossible(); + } + $this->writer->write($operation->getResult(), $format, $writeOptions); + } + + $io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths); + + // Append pulled translations to local ones. + $localTranslations->addBag($providerTranslations->diff($localTranslations)); + + foreach ($localTranslations->getCatalogues() as $catalogue) { + $this->writer->write($catalogue, $format, $writeOptions); + } + + $io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } +} diff --git a/libraries/Symfony/Component/Translation/Command/TranslationPushCommand.php b/libraries/Symfony/Component/Translation/Command/TranslationPushCommand.php new file mode 100644 index 00000000000..37f23a00cc6 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Command/TranslationPushCommand.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use EDD\Vendor\Symfony\Component\Translation\Provider\FilteringProvider; +use EDD\Vendor\Symfony\Component\Translation\Provider\TranslationProviderCollection; +use EDD\Vendor\Symfony\Component\Translation\Reader\TranslationReaderInterface; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBag; + +/** + * @author Mathieu Santostefano + */ +#[AsCommand(name: 'translation:push', description: 'Push translations to a given provider.')] +final class TranslationPushCommand extends Command +{ + use TranslationTrait; + + private TranslationProviderCollection $providers; + private TranslationReaderInterface $reader; + private array $transPaths; + private array $enabledLocales; + + public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = []) + { + $this->providers = $providers; + $this->reader = $reader; + $this->transPaths = $transPaths; + $this->enabledLocales = $enabledLocales; + + parent::__construct(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('provider')) { + $suggestions->suggestValues($this->providers->keys()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domains')) { + $provider = $this->providers->get($input->getArgument('provider')); + + if ($provider && method_exists($provider, 'getDomains')) { + $domains = $provider->getDomains(); + $suggestions->suggestValues($domains); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('locales')) { + $suggestions->suggestValues($this->enabledLocales); + } + } + + protected function configure(): void + { + $keys = $this->providers->keys(); + $defaultProvider = 1 === \count($keys) ? $keys[0] : null; + + $this + ->setDefinition([ + new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to push translations to.', $defaultProvider), + new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with local ones (it will delete not synchronized messages).'), + new InputOption('delete-missing', null, InputOption::VALUE_NONE, 'Delete translations available on provider but not locally.'), + new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'), + new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales), + ]) + ->setHelp(<<<'EOF' +The %command.name% command pushes translations to the given provider. Only new +translations are pushed, existing ones are not overwritten. + +You can overwrite existing translations by using the --force flag: + + php %command.full_name% --force provider + +You can delete provider translations which are not present locally by using the --delete-missing flag: + + php %command.full_name% --delete-missing provider + +Full example: + + php %command.full_name% provider --force --delete-missing --domains=messages --domains=validators --locales=en + +This command pushes all translations associated with the messages and validators domains for the en locale. +Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case. +Provider translations for others domains and locales are ignored. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $provider = $this->providers->get($input->getArgument('provider')); + + if (!$this->enabledLocales) { + throw new InvalidArgumentException(sprintf('You must define "framework.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME))); + } + + $io = new SymfonyStyle($input, $output); + $domains = $input->getOption('domains'); + $locales = $input->getOption('locales'); + $force = $input->getOption('force'); + $deleteMissing = $input->getOption('delete-missing'); + + if (!$domains && $provider instanceof FilteringProvider) { + $domains = $provider->getDomains(); + } + + // Reading local translations must be done after retrieving the domains from the provider + // in order to manage only translations from configured domains + $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths); + + if (!$domains) { + $domains = $this->getDomainsFromTranslatorBag($localTranslations); + } + + if (!$deleteMissing && $force) { + $provider->write($localTranslations); + + $io->success(sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + $providerTranslations = $provider->read($domains, $locales); + + if ($deleteMissing) { + $provider->delete($providerTranslations->diff($localTranslations)); + + $io->success(sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + // Read provider translations again, after missing translations deletion, + // to avoid push freshly deleted translations. + $providerTranslations = $provider->read($domains, $locales); + } + + $translationsToWrite = $localTranslations->diff($providerTranslations); + + if ($force) { + $translationsToWrite->addBag($localTranslations->intersect($providerTranslations)); + } + + $provider->write($translationsToWrite); + + $io->success(sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + private function getDomainsFromTranslatorBag(TranslatorBag $translatorBag): array + { + $domains = []; + + foreach ($translatorBag->getCatalogues() as $catalogue) { + $domains += $catalogue->getDomains(); + } + + return array_unique($domains); + } +} diff --git a/libraries/Symfony/Component/Translation/Command/TranslationTrait.php b/libraries/Symfony/Component/Translation/Command/TranslationTrait.php new file mode 100644 index 00000000000..5ef2aa2f47f --- /dev/null +++ b/libraries/Symfony/Component/Translation/Command/TranslationTrait.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Command; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogueInterface; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBag; + +/** + * @internal + */ +trait TranslationTrait +{ + private function readLocalTranslations(array $locales, array $domains, array $transPaths): TranslatorBag + { + $bag = new TranslatorBag(); + + foreach ($locales as $locale) { + $catalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + $this->reader->read($path, $catalogue); + } + + if ($domains) { + foreach ($domains as $domain) { + $bag->addCatalogue($this->filterCatalogue($catalogue, $domain)); + } + } else { + $bag->addCatalogue($catalogue); + } + } + + return $bag; + } + + private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue + { + $filteredCatalogue = new MessageCatalogue($catalogue->getLocale()); + + // extract intl-icu messages only + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + if ($intlMessages = $catalogue->all($intlDomain)) { + $filteredCatalogue->add($intlMessages, $intlDomain); + } + + // extract all messages and subtract intl-icu messages + if ($messages = array_diff($catalogue->all($domain), $intlMessages)) { + $filteredCatalogue->add($messages, $domain); + } + foreach ($catalogue->getResources() as $resource) { + $filteredCatalogue->addResource($resource); + } + + if ($metadata = $catalogue->getMetadata('', $intlDomain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $intlDomain); + } + } + + if ($metadata = $catalogue->getMetadata('', $domain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $domain); + } + } + + return $filteredCatalogue; + } +} diff --git a/libraries/Symfony/Component/Translation/Command/XliffLintCommand.php b/libraries/Symfony/Component/Translation/Command/XliffLintCommand.php new file mode 100644 index 00000000000..82f99cb9f2f --- /dev/null +++ b/libraries/Symfony/Component/Translation/Command/XliffLintCommand.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\Util\XliffUtils; + +/** + * Validates XLIFF files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + * @author Javier Eguiluz + */ +#[AsCommand(name: 'lint:xliff', description: 'Lint an XLIFF file and outputs encountered errors')] +class XliffLintCommand extends Command +{ + private string $format; + private bool $displayCorrectFiles; + private ?\Closure $directoryIteratorProvider; + private ?\Closure $isReadableProvider; + private bool $requireStrictFileNames; + + public function __construct(?string $name = null, ?callable $directoryIteratorProvider = null, ?callable $isReadableProvider = null, bool $requireStrictFileNames = true) + { + parent::__construct($name); + + $this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...); + $this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...); + $this->requireStrictFileNames = $requireStrictFileNames; + } + + /** + * @return void + */ + protected function configure() + { + $this + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) + ->setHelp(<<%command.name% command lints an XLIFF file and outputs to STDOUT +the first encountered syntax error. + +You can validates XLIFF contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'))]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $file); + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, ?string $file = null): array + { + $errors = []; + + // Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input + if ('' === trim($content)) { + return ['file' => $file, 'valid' => true]; + } + + $internal = libxml_use_internal_errors(true); + + $document = new \DOMDocument(); + $document->loadXML($content); + + if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) { + $normalizedLocalePattern = sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/')); + // strict file names require translation files to be named '____.locale.xlf' + // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed + // also, the regexp matching must be case-insensitive, as defined for 'target-language' values + // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language + $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern); + + if (0 === preg_match($expectedFilenamePattern, basename($file))) { + $errors[] = [ + 'line' => -1, + 'column' => -1, + 'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage), + ]; + } + } + + foreach (XliffUtils::validateSchema($document) as $xmlError) { + $errors[] = [ + 'line' => $xmlError['line'], + 'column' => $xmlError['column'], + 'message' => $xmlError['message'], + ]; + } + + libxml_clear_errors(); + libxml_use_internal_errors($internal); + + return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors]; + } + + private function display(SymfonyStyle $io, array $files): int + { + return match ($this->format) { + 'txt' => $this->displayTxt($io, $files), + 'json' => $this->displayJson($io, $files), + 'github' => $this->displayTxt($io, $files, true), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + }; + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($io) : null; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->listing(array_map(function ($error) use ($info, $githubReporter) { + // general document errors have a '-1' line number + $line = -1 === $error['line'] ? null : $error['line']; + + $githubReporter?->error($error['message'], $info['file'], $line, null !== $line ? $error['column'] : null); + + return null === $line ? $error['message'] : sprintf('Line %d, Column %d: %s', $line, $error['column'], $error['message']); + }, $info['messages'])); + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d XLIFF files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo): int + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + /** + * @return iterable<\SplFileInfo> + */ + private function getFiles(string $fileOrDirectory): iterable + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) { + continue; + } + + yield $file; + } + } + + /** + * @return iterable<\SplFileInfo> + */ + private function getDirectoryIterator(string $directory): iterable + { + $default = fn ($directory) => new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory): bool + { + $default = fn ($fileOrDirectory) => is_readable($fileOrDirectory); + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } + + private function getTargetLanguageFromFile(\DOMDocument $xliffContents): ?string + { + foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) { + if ('target-language' === $attribute->nodeName) { + return $attribute->nodeValue; + } + } + + return null; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function getAvailableFormatOptions(): array + { + return ['txt', 'json', 'github']; + } +} diff --git a/libraries/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php b/libraries/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php new file mode 100644 index 00000000000..1837e2823fd --- /dev/null +++ b/libraries/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use EDD\Vendor\Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Abdellatif Ait boudad + * + * @final + */ +class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private DataCollectorTranslator $translator; + + public function __construct(DataCollectorTranslator $translator) + { + $this->translator = $translator; + } + + public function lateCollect(): void + { + $messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages()); + + $this->data += $this->computeCount($messages); + $this->data['messages'] = $messages; + + $this->data = $this->cloneVar($this->data); + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $this->data['locale'] = $this->translator->getLocale(); + $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); + } + + public function reset(): void + { + $this->data = []; + } + + public function getMessages(): array|Data + { + return $this->data['messages'] ?? []; + } + + public function getCountMissings(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_MISSING] ?? 0; + } + + public function getCountFallbacks(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] ?? 0; + } + + public function getCountDefines(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_DEFINED] ?? 0; + } + + public function getLocale(): ?string + { + return !empty($this->data['locale']) ? $this->data['locale'] : null; + } + + /** + * @internal + */ + public function getFallbackLocales(): Data|array + { + return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : []; + } + + public function getName(): string + { + return 'translation'; + } + + private function sanitizeCollectedMessages(array $messages): array + { + $result = []; + foreach ($messages as $key => $message) { + $messageId = $message['locale'].$message['domain'].$message['id']; + + if (!isset($result[$messageId])) { + $message['count'] = 1; + $message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : []; + $messages[$key]['translation'] = $this->sanitizeString($message['translation']); + $result[$messageId] = $message; + } else { + if (!empty($message['parameters'])) { + $result[$messageId]['parameters'][] = $message['parameters']; + } + + ++$result[$messageId]['count']; + } + + unset($messages[$key]); + } + + return $result; + } + + private function computeCount(array $messages): array + { + $count = [ + DataCollectorTranslator::MESSAGE_DEFINED => 0, + DataCollectorTranslator::MESSAGE_MISSING => 0, + DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0, + ]; + + foreach ($messages as $message) { + ++$count[$message['state']]; + } + + return $count; + } + + private function sanitizeString(string $string, int $length = 80): string + { + $string = trim(preg_replace('/\s+/', ' ', $string)); + + if (false !== $encoding = mb_detect_encoding($string, null, true)) { + if (mb_strlen($string, $encoding) > $length) { + return mb_substr($string, 0, $length - 3, $encoding).'...'; + } + } elseif (\strlen($string) > $length) { + return substr($string, 0, $length - 3).'...'; + } + + return $string; + } +} diff --git a/libraries/Symfony/Component/Translation/DataCollectorTranslator.php b/libraries/Symfony/Component/Translation/DataCollectorTranslator.php new file mode 100644 index 00000000000..28ac84fd851 --- /dev/null +++ b/libraries/Symfony/Component/Translation/DataCollectorTranslator.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Contracts\Translation\LocaleAwareInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Abdellatif Ait boudad + */ +class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface +{ + public const MESSAGE_DEFINED = 0; + public const MESSAGE_MISSING = 1; + public const MESSAGE_EQUALS_FALLBACK = 2; + + private TranslatorInterface $translator; + private array $messages = []; + + /** + * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator + */ + public function __construct(TranslatorInterface $translator) + { + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator))); + } + + $this->translator = $translator; + } + + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale); + $this->collectMessage($locale, $domain, $id, $trans, $parameters); + + return $trans; + } + + /** + * @return void + */ + public function setLocale(string $locale) + { + $this->translator->setLocale($locale); + } + + public function getLocale(): string + { + return $this->translator->getLocale(); + } + + public function getCatalogue(?string $locale = null): MessageCatalogueInterface + { + return $this->translator->getCatalogue($locale); + } + + public function getCatalogues(): array + { + return $this->translator->getCatalogues(); + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + if ($this->translator instanceof WarmableInterface) { + return (array) $this->translator->warmUp($cacheDir, $buildDir); + } + + return []; + } + + /** + * Gets the fallback locales. + */ + public function getFallbackLocales(): array + { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { + return $this->translator->getFallbackLocales(); + } + + return []; + } + + /** + * @return mixed + */ + public function __call(string $method, array $args) + { + return $this->translator->{$method}(...$args); + } + + public function getCollectedMessages(): array + { + return $this->messages; + } + + private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = []): void + { + $domain ??= 'messages'; + + $catalogue = $this->translator->getCatalogue($locale); + $locale = $catalogue->getLocale(); + $fallbackLocale = null; + if ($catalogue->defines($id, $domain)) { + $state = self::MESSAGE_DEFINED; + } elseif ($catalogue->has($id, $domain)) { + $state = self::MESSAGE_EQUALS_FALLBACK; + + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + if ($fallbackCatalogue->defines($id, $domain)) { + $fallbackLocale = $fallbackCatalogue->getLocale(); + break; + } + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + } else { + $state = self::MESSAGE_MISSING; + } + + $this->messages[] = [ + 'locale' => $locale, + 'fallbackLocale' => $fallbackLocale, + 'domain' => $domain, + 'id' => $id, + 'translation' => $translation, + 'parameters' => $parameters, + 'state' => $state, + 'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null, + ]; + } +} diff --git a/libraries/Symfony/Component/Translation/DependencyInjection/DataCollectorTranslatorPass.php b/libraries/Symfony/Component/Translation/DependencyInjection/DataCollectorTranslatorPass.php new file mode 100644 index 00000000000..924b45ddf50 --- /dev/null +++ b/libraries/Symfony/Component/Translation/DependencyInjection/DataCollectorTranslatorPass.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBagInterface; + +/** + * @author Christian Flothmann + */ +class DataCollectorTranslatorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->has('translator')) { + return; + } + + $translatorClass = $container->getParameterBag()->resolveValue($container->findDefinition('translator')->getClass()); + + if (!is_subclass_of($translatorClass, TranslatorBagInterface::class)) { + $container->removeDefinition('translator.data_collector'); + $container->removeDefinition('data_collector.translation'); + } + } +} diff --git a/libraries/Symfony/Component/Translation/DependencyInjection/LoggingTranslatorPass.php b/libraries/Symfony/Component/Translation/DependencyInjection/LoggingTranslatorPass.php new file mode 100644 index 00000000000..ef286360a08 --- /dev/null +++ b/libraries/Symfony/Component/Translation/DependencyInjection/LoggingTranslatorPass.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBagInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Abdellatif Ait boudad + */ +class LoggingTranslatorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasAlias('logger') || !$container->hasAlias('translator')) { + return; + } + + if (!$container->hasParameter('translator.logging') || !$container->getParameter('translator.logging')) { + return; + } + + $translatorAlias = $container->getAlias('translator'); + $definition = $container->getDefinition((string) $translatorAlias); + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $translatorAlias)); + } + + if (!$r->isSubclassOf(TranslatorInterface::class) || !$r->isSubclassOf(TranslatorBagInterface::class)) { + return; + } + + $container->getDefinition('translator.logging')->setDecoratedService('translator'); + $warmer = $container->getDefinition('translation.warmer'); + $subscriberAttributes = $warmer->getTag('container.service_subscriber'); + $warmer->clearTag('container.service_subscriber'); + + foreach ($subscriberAttributes as $k => $v) { + if ((!isset($v['id']) || 'translator' !== $v['id']) && (!isset($v['key']) || 'translator' !== $v['key'])) { + $warmer->addTag('container.service_subscriber', $v); + } + } + $warmer->addTag('container.service_subscriber', ['key' => 'translator', 'id' => 'translator.logging.inner']); + } +} diff --git a/libraries/Symfony/Component/Translation/DependencyInjection/TranslationDumperPass.php b/libraries/Symfony/Component/Translation/DependencyInjection/TranslationDumperPass.php new file mode 100644 index 00000000000..79eae4d89b7 --- /dev/null +++ b/libraries/Symfony/Component/Translation/DependencyInjection/TranslationDumperPass.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged translation.formatter services to translation writer. + */ +class TranslationDumperPass implements CompilerPassInterface +{ + /** + * @return void + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('translation.writer')) { + return; + } + + $definition = $container->getDefinition('translation.writer'); + + foreach ($container->findTaggedServiceIds('translation.dumper', true) as $id => $attributes) { + $definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]); + } + } +} diff --git a/libraries/Symfony/Component/Translation/DependencyInjection/TranslationExtractorPass.php b/libraries/Symfony/Component/Translation/DependencyInjection/TranslationExtractorPass.php new file mode 100644 index 00000000000..4c5e1e926bf --- /dev/null +++ b/libraries/Symfony/Component/Translation/DependencyInjection/TranslationExtractorPass.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged translation.extractor services to translation extractor. + */ +class TranslationExtractorPass implements CompilerPassInterface +{ + /** + * @return void + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('translation.extractor')) { + return; + } + + $definition = $container->getDefinition('translation.extractor'); + + foreach ($container->findTaggedServiceIds('translation.extractor', true) as $id => $attributes) { + if (!isset($attributes[0]['alias'])) { + throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id)); + } + + $definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]); + } + } +} diff --git a/libraries/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php b/libraries/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php new file mode 100644 index 00000000000..2aece562ec2 --- /dev/null +++ b/libraries/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class TranslatorPass implements CompilerPassInterface +{ + /** + * @return void + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('translator.default')) { + return; + } + + $loaders = []; + $loaderRefs = []; + foreach ($container->findTaggedServiceIds('translation.loader', true) as $id => $attributes) { + $loaderRefs[$id] = new Reference($id); + $loaders[$id][] = $attributes[0]['alias']; + if (isset($attributes[0]['legacy-alias'])) { + $loaders[$id][] = $attributes[0]['legacy-alias']; + } + } + + if ($container->hasDefinition('translation.reader')) { + $definition = $container->getDefinition('translation.reader'); + foreach ($loaders as $id => $formats) { + foreach ($formats as $format) { + $definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]); + } + } + } + + $container + ->findDefinition('translator.default') + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs)) + ->replaceArgument(3, $loaders) + ; + + if ($container->hasDefinition('validator') && $container->hasDefinition('translation.extractor.visitor.constraint')) { + $constraintVisitorDefinition = $container->getDefinition('translation.extractor.visitor.constraint'); + $constraintClassNames = []; + + foreach ($container->getDefinitions() as $definition) { + if (!$definition->hasTag('validator.constraint_validator')) { + continue; + } + // Resolve constraint validator FQCN even if defined as %foo.validator.class% parameter + $className = $container->getParameterBag()->resolveValue($definition->getClass()); + // Extraction of the constraint class name from the Constraint Validator FQCN + $constraintClassNames[] = str_replace('Validator', '', substr(strrchr($className, '\\'), 1)); + } + + $constraintVisitorDefinition->setArgument(0, $constraintClassNames); + } + + if (!$container->hasParameter('twig.default_path')) { + return; + } + + $paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(1)); + if ($container->hasDefinition('console.command.translation_debug')) { + $definition = $container->getDefinition('console.command.translation_debug'); + $definition->replaceArgument(4, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 6) { + $definition->replaceArgument(6, $paths); + } + } + if ($container->hasDefinition('console.command.translation_extract')) { + $definition = $container->getDefinition('console.command.translation_extract'); + $definition->replaceArgument(5, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 7) { + $definition->replaceArgument(7, $paths); + } + } + } +} diff --git a/libraries/Symfony/Component/Translation/DependencyInjection/TranslatorPathsPass.php b/libraries/Symfony/Component/Translation/DependencyInjection/TranslatorPathsPass.php new file mode 100644 index 00000000000..f0f07b38c9e --- /dev/null +++ b/libraries/Symfony/Component/Translation/DependencyInjection/TranslatorPathsPass.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; + +/** + * @author Yonel Ceruto + */ +class TranslatorPathsPass extends AbstractRecursivePass +{ + protected bool $skipScalars = true; + + private int $level = 0; + + /** + * @var array + */ + private array $paths = []; + + /** + * @var array + */ + private array $definitions = []; + + /** + * @var array> + */ + private array $controllers = []; + + /** + * @return void + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('translator')) { + return; + } + + foreach ($this->findControllerArguments($container) as $controller => $argument) { + $id = substr($controller, 0, strpos($controller, ':') ?: \strlen($controller)); + if ($container->hasDefinition($id)) { + [$locatorRef] = $argument->getValues(); + $this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true; + } + } + + try { + parent::process($container); + + $paths = []; + foreach ($this->paths as $class => $_) { + if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) { + $paths[] = $r->getFileName(); + foreach ($r->getTraits() as $trait) { + $paths[] = $trait->getFileName(); + } + } + } + if ($paths) { + if ($container->hasDefinition('console.command.translation_debug')) { + $definition = $container->getDefinition('console.command.translation_debug'); + $definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths)); + } + if ($container->hasDefinition('console.command.translation_extract')) { + $definition = $container->getDefinition('console.command.translation_extract'); + $definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths)); + } + } + } finally { + $this->level = 0; + $this->paths = []; + $this->definitions = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof Reference) { + if ('translator' === (string) $value) { + for ($i = $this->level - 1; $i >= 0; --$i) { + $class = $this->definitions[$i]->getClass(); + + if (ServiceLocator::class === $class) { + if (!isset($this->controllers[$this->currentId])) { + continue; + } + foreach ($this->controllers[$this->currentId] as $class => $_) { + $this->paths[$class] = true; + } + } else { + $this->paths[$class] = true; + } + + break; + } + } + + return $value; + } + + if ($value instanceof Definition) { + $this->definitions[$this->level++] = $value; + $value = parent::processValue($value, $isRoot); + unset($this->definitions[--$this->level]); + + return $value; + } + + return parent::processValue($value, $isRoot); + } + + private function findControllerArguments(ContainerBuilder $container): array + { + if (!$container->has('argument_resolver.service')) { + return []; + } + $resolverDef = $container->findDefinition('argument_resolver.service'); + + if (TraceableValueResolver::class === $resolverDef->getClass()) { + $resolverDef = $container->getDefinition($resolverDef->getArgument(0)); + } + + $argument = $resolverDef->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + + return $argument->getArgument(0); + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/CsvFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/CsvFileDumper.php new file mode 100644 index 00000000000..bcacb3d9ba8 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/CsvFileDumper.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * CsvFileDumper generates a csv formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class CsvFileDumper extends FileDumper +{ + private string $delimiter = ';'; + private string $enclosure = '"'; + + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $handle = fopen('php://memory', 'r+'); + + foreach ($messages->all($domain) as $source => $target) { + fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure, '\\'); + } + + rewind($handle); + $output = stream_get_contents($handle); + fclose($handle); + + return $output; + } + + /** + * Sets the delimiter and escape character for CSV. + * + * @return void + */ + public function setCsvControl(string $delimiter = ';', string $enclosure = '"') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + } + + protected function getExtension(): string + { + return 'csv'; + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/DumperInterface.php b/libraries/Symfony/Component/Translation/Dumper/DumperInterface.php new file mode 100644 index 00000000000..186b2b884dd --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/DumperInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * DumperInterface is the interface implemented by all translation dumpers. + * There is no common option. + * + * @author Michel Salib + */ +interface DumperInterface +{ + /** + * Dumps the message catalogue. + * + * @param array $options Options that are used by the dumper + * + * @return void + */ + public function dump(MessageCatalogue $messages, array $options = []); +} diff --git a/libraries/Symfony/Component/Translation/Dumper/FileDumper.php b/libraries/Symfony/Component/Translation/Dumper/FileDumper.php new file mode 100644 index 00000000000..1a28a511921 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/FileDumper.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\Exception\RuntimeException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s). + * + * Options: + * - path (mandatory): the directory where the files should be saved + * + * @author Michel Salib + */ +abstract class FileDumper implements DumperInterface +{ + /** + * A template for the relative paths to files. + * + * @var string + */ + protected $relativePathTemplate = '%domain%.%locale%.%extension%'; + + /** + * Sets the template for the relative paths to files. + * + * @return void + */ + public function setRelativePathTemplate(string $relativePathTemplate) + { + $this->relativePathTemplate = $relativePathTemplate; + } + + /** + * @return void + */ + public function dump(MessageCatalogue $messages, array $options = []) + { + if (!\array_key_exists('path', $options)) { + throw new InvalidArgumentException('The file dumper needs a path option.'); + } + + // save a file for each domain + foreach ($messages->getDomains() as $domain) { + $fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale()); + if (!file_exists($fullpath)) { + $directory = \dirname($fullpath); + if (!file_exists($directory) && !@mkdir($directory, 0777, true)) { + throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory)); + } + } + + $intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX; + $intlMessages = $messages->all($intlDomain); + + if ($intlMessages) { + $intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale()); + file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options)); + + $messages->replace([], $intlDomain); + + try { + if ($messages->all($domain)) { + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + continue; + } finally { + $messages->replace($intlMessages, $intlDomain); + } + } + + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + } + + /** + * Transforms a domain of a message catalogue to its string representation. + */ + abstract public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string; + + /** + * Gets the file extension of the dumper. + */ + abstract protected function getExtension(): string; + + /** + * Gets the relative file path using the template. + */ + private function getRelativePath(string $domain, string $locale): string + { + return strtr($this->relativePathTemplate, [ + '%domain%' => $domain, + '%locale%' => $locale, + '%extension%' => $this->getExtension(), + ]); + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/IcuResFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/IcuResFileDumper.php new file mode 100644 index 00000000000..f2876dd25c9 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/IcuResFileDumper.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResDumper generates an ICU ResourceBundle formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IcuResFileDumper extends FileDumper +{ + protected $relativePathTemplate = '%domain%/%locale%.%extension%'; + + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $data = $indexes = $resources = ''; + + foreach ($messages->all($domain) as $source => $target) { + $indexes .= pack('v', \strlen($data) + 28); + $data .= $source."\0"; + } + + $data .= $this->writePadding($data); + + $keyTop = $this->getPosition($data); + + foreach ($messages->all($domain) as $source => $target) { + $resources .= pack('V', $this->getPosition($data)); + + $data .= pack('V', \strlen($target)) + .mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8') + .$this->writePadding($data) + ; + } + + $resOffset = $this->getPosition($data); + + $data .= pack('v', \count($messages->all($domain))) + .$indexes + .$this->writePadding($data) + .$resources + ; + + $bundleTop = $this->getPosition($data); + + $root = pack('V7', + $resOffset + (2 << 28), // Resource Offset + Resource Type + 6, // Index length + $keyTop, // Index keys top + $bundleTop, // Index resources top + $bundleTop, // Index bundle top + \count($messages->all($domain)), // Index max table length + 0 // Index attributes + ); + + $header = pack('vC2v4C12@32', + 32, // Header size + 0xDA, 0x27, // Magic number 1 and 2 + 20, 0, 0, 2, // Rest of the header, ..., Size of a char + 0x52, 0x65, 0x73, 0x42, // Data format identifier + 1, 2, 0, 0, // Data version + 1, 4, 0, 0 // Unicode version + ); + + return $header.$root.$data; + } + + private function writePadding(string $data): ?string + { + $padding = \strlen($data) % 4; + + return $padding ? str_repeat("\xAA", 4 - $padding) : null; + } + + private function getPosition(string $data): float|int + { + return (\strlen($data) + 28) / 4; + } + + protected function getExtension(): string + { + return 'res'; + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/IniFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/IniFileDumper.php new file mode 100644 index 00000000000..e4a4efdc00f --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/IniFileDumper.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * IniFileDumper generates an ini formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IniFileDumper extends FileDumper +{ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $output = ''; + + foreach ($messages->all($domain) as $source => $target) { + $escapeTarget = str_replace('"', '\"', $target); + $output .= $source.'="'.$escapeTarget."\"\n"; + } + + return $output; + } + + protected function getExtension(): string + { + return 'ini'; + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/JsonFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/JsonFileDumper.php new file mode 100644 index 00000000000..e59958d1ea2 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/JsonFileDumper.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * JsonFileDumper generates an json formatted string representation of a message catalogue. + * + * @author singles + */ +class JsonFileDumper extends FileDumper +{ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT; + + return json_encode($messages->all($domain), $flags); + } + + protected function getExtension(): string + { + return 'json'; + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/MoFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/MoFileDumper.php new file mode 100644 index 00000000000..9835a7aca56 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/MoFileDumper.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\Loader\MoFileLoader; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * MoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class MoFileDumper extends FileDumper +{ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $sources = $targets = $sourceOffsets = $targetOffsets = ''; + $offsets = []; + $size = 0; + + foreach ($messages->all($domain) as $source => $target) { + $offsets[] = array_map('strlen', [$sources, $source, $targets, $target]); + $sources .= "\0".$source; + $targets .= "\0".$target; + ++$size; + } + + $header = [ + 'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC, + 'formatRevision' => 0, + 'count' => $size, + 'offsetId' => MoFileLoader::MO_HEADER_SIZE, + 'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size), + 'sizeHashes' => 0, + 'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size), + ]; + + $sourcesSize = \strlen($sources); + $sourcesStart = $header['offsetHashes'] + 1; + + foreach ($offsets as $offset) { + $sourceOffsets .= $this->writeLong($offset[1]) + .$this->writeLong($offset[0] + $sourcesStart); + $targetOffsets .= $this->writeLong($offset[3]) + .$this->writeLong($offset[2] + $sourcesStart + $sourcesSize); + } + + $output = implode('', array_map($this->writeLong(...), $header)) + .$sourceOffsets + .$targetOffsets + .$sources + .$targets + ; + + return $output; + } + + protected function getExtension(): string + { + return 'mo'; + } + + private function writeLong(mixed $str): string + { + return pack('V*', $str); + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/PhpFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/PhpFileDumper.php new file mode 100644 index 00000000000..06208e90c33 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/PhpFileDumper.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpFileDumper generates PHP files from a message catalogue. + * + * @author Michel Salib + */ +class PhpFileDumper extends FileDumper +{ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + return "all($domain), true).";\n"; + } + + protected function getExtension(): string + { + return 'php'; + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/PoFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/PoFileDumper.php new file mode 100644 index 00000000000..b2d622f96a2 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/PoFileDumper.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * PoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class PoFileDumper extends FileDumper +{ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $output = 'msgid ""'."\n"; + $output .= 'msgstr ""'."\n"; + $output .= '"Content-Type: text/plain; charset=UTF-8\n"'."\n"; + $output .= '"Content-Transfer-Encoding: 8bit\n"'."\n"; + $output .= '"Language: '.$messages->getLocale().'\n"'."\n"; + $output .= "\n"; + + $newLine = false; + foreach ($messages->all($domain) as $source => $target) { + if ($newLine) { + $output .= "\n"; + } else { + $newLine = true; + } + $metadata = $messages->getMetadata($source, $domain); + + if (isset($metadata['comments'])) { + $output .= $this->formatComments($metadata['comments']); + } + if (isset($metadata['flags'])) { + $output .= $this->formatComments(implode(',', (array) $metadata['flags']), ','); + } + if (isset($metadata['sources'])) { + $output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':'); + } + + $sourceRules = $this->getStandardRules($source); + $targetRules = $this->getStandardRules($target); + if (2 == \count($sourceRules) && [] !== $targetRules) { + $output .= sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0])); + $output .= sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1])); + foreach ($targetRules as $i => $targetRule) { + $output .= sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule)); + } + } else { + $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); + $output .= sprintf('msgstr "%s"'."\n", $this->escape($target)); + } + } + + return $output; + } + + private function getStandardRules(string $id): array + { + // Partly copied from TranslatorTrait::trans. + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + if (preg_match($intervalRegexp, $part)) { + // Explicit rule is not a standard rule. + return []; + } else { + $standardRules[] = $part; + } + } + + return $standardRules; + } + + protected function getExtension(): string + { + return 'po'; + } + + private function escape(string $str): string + { + return addcslashes($str, "\0..\37\42\134"); + } + + private function formatComments(string|array $comments, string $prefix = ''): ?string + { + $output = null; + + foreach ((array) $comments as $comment) { + $output .= sprintf('#%s %s'."\n", $prefix, $comment); + } + + return $output; + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/QtFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/QtFileDumper.php new file mode 100644 index 00000000000..9f273b5ddfb --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/QtFileDumper.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * QtFileDumper generates ts files from a message catalogue. + * + * @author Benjamin Eberlei + */ +class QtFileDumper extends FileDumper +{ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + $ts = $dom->appendChild($dom->createElement('TS')); + $context = $ts->appendChild($dom->createElement('context')); + $context->appendChild($dom->createElement('name', $domain)); + + foreach ($messages->all($domain) as $source => $target) { + $message = $context->appendChild($dom->createElement('message')); + $metadata = $messages->getMetadata($source, $domain); + if (isset($metadata['sources'])) { + foreach ((array) $metadata['sources'] as $location) { + $loc = explode(':', $location, 2); + $location = $message->appendChild($dom->createElement('location')); + $location->setAttribute('filename', $loc[0]); + if (isset($loc[1])) { + $location->setAttribute('line', $loc[1]); + } + } + } + $message->appendChild($dom->createElement('source', $source)); + $message->appendChild($dom->createElement('translation', $target)); + } + + return $dom->saveXML(); + } + + protected function getExtension(): string + { + return 'ts'; + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/XliffFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/XliffFileDumper.php new file mode 100644 index 00000000000..8c028f59840 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/XliffFileDumper.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * XliffFileDumper generates xliff files from a message catalogue. + * + * @author Michel Salib + */ +class XliffFileDumper extends FileDumper +{ + public function __construct( + private string $extension = 'xlf', + ) { + } + + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $xliffVersion = '1.2'; + if (\array_key_exists('xliff_version', $options)) { + $xliffVersion = $options['xliff_version']; + } + + if (\array_key_exists('default_locale', $options)) { + $defaultLocale = $options['default_locale']; + } else { + $defaultLocale = \Locale::getDefault(); + } + + if ('1.2' === $xliffVersion) { + return $this->dumpXliff1($defaultLocale, $messages, $domain, $options); + } + if ('2.0' === $xliffVersion) { + return $this->dumpXliff2($defaultLocale, $messages, $domain); + } + + throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); + } + + protected function getExtension(): string + { + return $this->extension; + } + + private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = []): string + { + $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony']; + if (\array_key_exists('tool_info', $options)) { + $toolInfo = array_merge($toolInfo, $options['tool_info']); + } + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('version', '1.2'); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2'); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + $xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale)); + $xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale())); + $xliffFile->setAttribute('datatype', 'plaintext'); + $xliffFile->setAttribute('original', 'file.ext'); + + $xliffHead = $xliffFile->appendChild($dom->createElement('header')); + $xliffTool = $xliffHead->appendChild($dom->createElement('tool')); + foreach ($toolInfo as $id => $value) { + $xliffTool->setAttribute($id, $value); + } + + if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) { + $xliffPropGroup = $xliffHead->appendChild($dom->createElement('prop-group')); + foreach ($catalogueMetadata as $key => $value) { + $xliffProp = $xliffPropGroup->appendChild($dom->createElement('prop')); + $xliffProp->setAttribute('prop-type', $key); + $xliffProp->appendChild($dom->createTextNode($value)); + } + } + + $xliffBody = $xliffFile->appendChild($dom->createElement('body')); + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('trans-unit'); + + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + $translation->setAttribute('resname', $source); + + $s = $translation->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + $metadata = $messages->getMetadata($source, $domain); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $translation->appendChild($targetElement); + $t->appendChild($text); + + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + foreach ($metadata['notes'] as $note) { + if (!isset($note['content'])) { + continue; + } + + $n = $translation->appendChild($dom->createElement('note')); + $n->appendChild($dom->createTextNode($note['content'])); + + if (isset($note['priority'])) { + $n->setAttribute('priority', $note['priority']); + } + + if (isset($note['from'])) { + $n->setAttribute('from', $note['from']); + } + } + } + + $xliffBody->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain): string + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0'); + $xliff->setAttribute('version', '2.0'); + $xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale)); + $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale())); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale()); + } else { + $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale()); + } + + if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) { + $xliff->setAttribute('xmlns:m', 'urn:oasis:names:tc:xliff:metadata:2.0'); + $xliffMetadata = $xliffFile->appendChild($dom->createElement('m:metadata')); + foreach ($catalogueMetadata as $key => $value) { + $xliffMeta = $xliffMetadata->appendChild($dom->createElement('prop')); + $xliffMeta->setAttribute('type', $key); + $xliffMeta->appendChild($dom->createTextNode($value)); + } + } + + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('unit'); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + + if (\strlen($source) <= 80) { + $translation->setAttribute('name', $source); + } + + $metadata = $messages->getMetadata($source, $domain); + + // Add notes section + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + $notesElement = $dom->createElement('notes'); + foreach ($metadata['notes'] as $note) { + $n = $dom->createElement('note'); + $n->appendChild($dom->createTextNode($note['content'] ?? '')); + unset($note['content']); + + foreach ($note as $name => $value) { + $n->setAttribute($name, $value); + } + $notesElement->appendChild($n); + } + $translation->appendChild($notesElement); + } + + $segment = $translation->appendChild($dom->createElement('segment')); + + $s = $segment->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $segment->appendChild($targetElement); + $t->appendChild($text); + + $xliffFile->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function hasMetadataArrayInfo(string $key, ?array $metadata = null): bool + { + return is_iterable($metadata[$key] ?? null); + } +} diff --git a/libraries/Symfony/Component/Translation/Dumper/YamlFileDumper.php b/libraries/Symfony/Component/Translation/Dumper/YamlFileDumper.php new file mode 100644 index 00000000000..c2995a95d05 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Dumper/YamlFileDumper.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Dumper; + +use EDD\Vendor\Symfony\Component\Translation\Exception\LogicException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; +use EDD\Vendor\Symfony\Component\Translation\Util\ArrayConverter; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileDumper generates yaml files from a message catalogue. + * + * @author Michel Salib + */ +class YamlFileDumper extends FileDumper +{ + private string $extension; + + public function __construct(string $extension = 'yml') + { + $this->extension = $extension; + } + + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + if (!class_exists(Yaml::class)) { + throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.'); + } + + $data = $messages->all($domain); + + if (isset($options['as_tree']) && $options['as_tree']) { + $data = ArrayConverter::expandToTree($data); + } + + if (isset($options['inline']) && ($inline = (int) $options['inline']) > 0) { + return Yaml::dump($data, $inline); + } + + return Yaml::dump($data); + } + + protected function getExtension(): string + { + return $this->extension; + } +} diff --git a/libraries/Symfony/Component/Translation/Exception/ExceptionInterface.php b/libraries/Symfony/Component/Translation/Exception/ExceptionInterface.php new file mode 100644 index 00000000000..39d10d90953 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/libraries/Symfony/Component/Translation/Exception/IncompleteDsnException.php b/libraries/Symfony/Component/Translation/Exception/IncompleteDsnException.php new file mode 100644 index 00000000000..2385af103d5 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/IncompleteDsnException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +class IncompleteDsnException extends InvalidArgumentException +{ + public function __construct(string $message, ?string $dsn = null, ?\Throwable $previous = null) + { + if ($dsn) { + $message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message; + } + + parent::__construct($message, 0, $previous); + } +} diff --git a/libraries/Symfony/Component/Translation/Exception/InvalidArgumentException.php b/libraries/Symfony/Component/Translation/Exception/InvalidArgumentException.php new file mode 100644 index 00000000000..ec91e42b654 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +/** + * Base InvalidArgumentException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/libraries/Symfony/Component/Translation/Exception/InvalidResourceException.php b/libraries/Symfony/Component/Translation/Exception/InvalidResourceException.php new file mode 100644 index 00000000000..85b1610ef70 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/InvalidResourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource cannot be loaded. + * + * @author Fabien Potencier + */ +class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/libraries/Symfony/Component/Translation/Exception/LogicException.php b/libraries/Symfony/Component/Translation/Exception/LogicException.php new file mode 100644 index 00000000000..271104fa5ac --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +/** + * Base LogicException for Translation component. + * + * @author Abdellatif Ait boudad + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/libraries/Symfony/Component/Translation/Exception/MissingRequiredOptionException.php b/libraries/Symfony/Component/Translation/Exception/MissingRequiredOptionException.php new file mode 100644 index 00000000000..439a20effe6 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/MissingRequiredOptionException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +/** + * @author Oskar Stark + */ +class MissingRequiredOptionException extends IncompleteDsnException +{ + public function __construct(string $option, ?string $dsn = null, ?\Throwable $previous = null) + { + $message = sprintf('The option "%s" is required but missing.', $option); + + parent::__construct($message, $dsn, $previous); + } +} diff --git a/libraries/Symfony/Component/Translation/Exception/NotFoundResourceException.php b/libraries/Symfony/Component/Translation/Exception/NotFoundResourceException.php new file mode 100644 index 00000000000..246af01c134 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/NotFoundResourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource does not exist. + * + * @author Fabien Potencier + */ +class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/libraries/Symfony/Component/Translation/Exception/ProviderException.php b/libraries/Symfony/Component/Translation/Exception/ProviderException.php new file mode 100644 index 00000000000..f46801d8d67 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/ProviderException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Fabien Potencier + */ +class ProviderException extends RuntimeException implements ProviderExceptionInterface +{ + private ResponseInterface $response; + private string $debug; + + public function __construct(string $message, ResponseInterface $response, int $code = 0, ?\Exception $previous = null) + { + $this->response = $response; + $this->debug = $response->getInfo('debug') ?? ''; + + parent::__construct($message, $code, $previous); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } + + public function getDebug(): string + { + return $this->debug; + } +} diff --git a/libraries/Symfony/Component/Translation/Exception/ProviderExceptionInterface.php b/libraries/Symfony/Component/Translation/Exception/ProviderExceptionInterface.php new file mode 100644 index 00000000000..2b81c28a997 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/ProviderExceptionInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +/** + * @author Fabien Potencier + */ +interface ProviderExceptionInterface extends ExceptionInterface +{ + /* + * Returns debug info coming from the Symfony\Contracts\HttpClient\ResponseInterface + */ + public function getDebug(): string; +} diff --git a/libraries/Symfony/Component/Translation/Exception/RuntimeException.php b/libraries/Symfony/Component/Translation/Exception/RuntimeException.php new file mode 100644 index 00000000000..e832eebfc60 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +/** + * Base RuntimeException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/libraries/Symfony/Component/Translation/Exception/UnsupportedSchemeException.php b/libraries/Symfony/Component/Translation/Exception/UnsupportedSchemeException.php new file mode 100644 index 00000000000..5ae2413539c --- /dev/null +++ b/libraries/Symfony/Component/Translation/Exception/UnsupportedSchemeException.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Exception; + +use EDD\Vendor\Symfony\Component\Translation\Bridge; +use EDD\Vendor\Symfony\Component\Translation\Provider\Dsn; + +class UnsupportedSchemeException extends LogicException +{ + private const SCHEME_TO_PACKAGE_MAP = [ + 'crowdin' => [ + 'class' => Bridge\Crowdin\CrowdinProviderFactory::class, + 'package' => 'symfony/crowdin-translation-provider', + ], + 'loco' => [ + 'class' => Bridge\Loco\LocoProviderFactory::class, + 'package' => 'symfony/loco-translation-provider', + ], + 'lokalise' => [ + 'class' => Bridge\Lokalise\LokaliseProviderFactory::class, + 'package' => 'symfony/lokalise-translation-provider', + ], + 'phrase' => [ + 'class' => Bridge\Phrase\PhraseProviderFactory::class, + 'package' => 'symfony/phrase-translation-provider', + ], + ]; + + public function __construct(Dsn $dsn, ?string $name = null, array $supported = []) + { + $provider = $dsn->getScheme(); + if (false !== $pos = strpos($provider, '+')) { + $provider = substr($provider, 0, $pos); + } + $package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null; + if ($package && !class_exists($package['class'])) { + parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed. Try running "composer require %s".', $provider, $package['package'])); + + return; + } + + $message = sprintf('The "%s" scheme is not supported', $dsn->getScheme()); + if ($name && $supported) { + $message .= sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, implode('", "', $supported)); + } + + parent::__construct($message.'.'); + } +} diff --git a/libraries/Symfony/Component/Translation/Extractor/AbstractFileExtractor.php b/libraries/Symfony/Component/Translation/Extractor/AbstractFileExtractor.php new file mode 100644 index 00000000000..62d8829850d --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/AbstractFileExtractor.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * Base class used by classes that extract translation messages from files. + * + * @author Marcos D. Sánchez + */ +abstract class AbstractFileExtractor +{ + protected function extractFiles(string|iterable $resource): iterable + { + if (is_iterable($resource)) { + $files = []; + foreach ($resource as $file) { + if ($this->canBeExtracted($file)) { + $files[] = $this->toSplFileInfo($file); + } + } + } elseif (is_file($resource)) { + $files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : []; + } else { + $files = $this->extractFromDirectory($resource); + } + + return $files; + } + + private function toSplFileInfo(string $file): \SplFileInfo + { + return new \SplFileInfo($file); + } + + /** + * @throws InvalidArgumentException + */ + protected function isFile(string $file): bool + { + if (!is_file($file)) { + throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file)); + } + + return true; + } + + /** + * @return bool + */ + abstract protected function canBeExtracted(string $file); + + /** + * @return iterable + */ + abstract protected function extractFromDirectory(string|array $resource); +} diff --git a/libraries/Symfony/Component/Translation/Extractor/ChainExtractor.php b/libraries/Symfony/Component/Translation/Extractor/ChainExtractor.php new file mode 100644 index 00000000000..db28c2b9eea --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/ChainExtractor.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * ChainExtractor extracts translation messages from template files. + * + * @author Michel Salib + */ +class ChainExtractor implements ExtractorInterface +{ + /** + * The extractors. + * + * @var ExtractorInterface[] + */ + private array $extractors = []; + + /** + * Adds a loader to the translation extractor. + * + * @return void + */ + public function addExtractor(string $format, ExtractorInterface $extractor) + { + $this->extractors[$format] = $extractor; + } + + /** + * @return void + */ + public function setPrefix(string $prefix) + { + foreach ($this->extractors as $extractor) { + $extractor->setPrefix($prefix); + } + } + + /** + * @return void + */ + public function extract(string|iterable $directory, MessageCatalogue $catalogue) + { + foreach ($this->extractors as $extractor) { + $extractor->extract($directory, $catalogue); + } + } +} diff --git a/libraries/Symfony/Component/Translation/Extractor/ExtractorInterface.php b/libraries/Symfony/Component/Translation/Extractor/ExtractorInterface.php new file mode 100644 index 00000000000..a1e083f95d8 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/ExtractorInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * Extracts translation messages from a directory or files to the catalogue. + * New found messages are injected to the catalogue using the prefix. + * + * @author Michel Salib + */ +interface ExtractorInterface +{ + /** + * Extracts translation messages from files, a file or a directory to the catalogue. + * + * @param string|iterable $resource Files, a file or a directory + * + * @return void + */ + public function extract(string|iterable $resource, MessageCatalogue $catalogue); + + /** + * Sets the prefix that should be used for new found messages. + * + * @return void + */ + public function setPrefix(string $prefix); +} diff --git a/libraries/Symfony/Component/Translation/Extractor/PhpAstExtractor.php b/libraries/Symfony/Component/Translation/Extractor/PhpAstExtractor.php new file mode 100644 index 00000000000..7d9015eb7d9 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/PhpAstExtractor.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor; + +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor; +use PhpParser\Parser; +use PhpParser\ParserFactory; +use Symfony\Component\Finder\Finder; +use EDD\Vendor\Symfony\Component\Translation\Extractor\Visitor\AbstractVisitor; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpAstExtractor extracts translation messages from a PHP AST. + * + * @author Mathieu Santostefano + */ +final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorInterface +{ + private Parser $parser; + + public function __construct( + /** + * @param iterable $visitors + */ + private readonly iterable $visitors, + private string $prefix = '', + ) { + if (!class_exists(ParserFactory::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class)); + } + + $this->parser = (new ParserFactory())->createForHostVersion(); + } + + public function extract(iterable|string $resource, MessageCatalogue $catalogue): void + { + foreach ($this->extractFiles($resource) as $file) { + $traverser = new NodeTraverser(); + + // This is needed to resolve namespaces in class methods/constants. + $nameResolver = new NodeVisitor\NameResolver(); + $traverser->addVisitor($nameResolver); + + /** @var AbstractVisitor&NodeVisitor $visitor */ + foreach ($this->visitors as $visitor) { + $visitor->initialize($catalogue, $file, $this->prefix); + $traverser->addVisitor($visitor); + } + + $nodes = $this->parser->parse(file_get_contents($file)); + $traverser->traverse($nodes); + } + } + + public function setPrefix(string $prefix): void + { + $this->prefix = $prefix; + } + + protected function canBeExtracted(string $file): bool + { + return 'php' === pathinfo($file, \PATHINFO_EXTENSION) + && $this->isFile($file) + && preg_match('/\bt\(|->trans\(|TranslatableMessage|Symfony\\\\Component\\\\Validator\\\\Constraints/i', file_get_contents($file)); + } + + protected function extractFromDirectory(array|string $resource): iterable|Finder + { + if (!class_exists(Finder::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class)); + } + + return (new Finder())->files()->name('*.php')->in($resource); + } +} diff --git a/libraries/Symfony/Component/Translation/Extractor/PhpExtractor.php b/libraries/Symfony/Component/Translation/Extractor/PhpExtractor.php new file mode 100644 index 00000000000..c011afb381c --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/PhpExtractor.php @@ -0,0 +1,333 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor; + +trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated, use "%s" instead.', PhpExtractor::class, PhpAstExtractor::class); + +use Symfony\Component\Finder\Finder; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpExtractor extracts translation messages from a PHP template. + * + * @author Michel Salib + * + * @deprecated since Symfony 6.2, use the PhpAstExtractor instead + */ +class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface +{ + public const MESSAGE_TOKEN = 300; + public const METHOD_ARGUMENTS_TOKEN = 1000; + public const DOMAIN_TOKEN = 1001; + + /** + * Prefix for new found message. + */ + private string $prefix = ''; + + /** + * The sequence that captures translation messages. + */ + protected $sequences = [ + [ + '->', + 'trans', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + '->', + 'trans', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + '\\', + 'Symfony', + '\\', + 'Component', + '\\', + 'Translation', + '\\', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + '\EDD\Vendor\Symfony\Component\Translation\TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + '\\', + 'Symfony', + '\\', + 'Component', + '\\', + 'Translation', + '\\', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + '\EDD\Vendor\Symfony\Component\Translation\TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 't', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 't', + '(', + self::MESSAGE_TOKEN, + ], + ]; + + /** + * @return void + */ + public function extract(string|iterable $resource, MessageCatalogue $catalog) + { + $files = $this->extractFiles($resource); + foreach ($files as $file) { + $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file); + + gc_mem_caches(); + } + } + + /** + * @return void + */ + public function setPrefix(string $prefix) + { + $this->prefix = $prefix; + } + + /** + * Normalizes a token. + */ + protected function normalizeToken(mixed $token): ?string + { + if (isset($token[1]) && 'b"' !== $token) { + return $token[1]; + } + + return $token; + } + + /** + * Seeks to a non-whitespace token. + */ + private function seekToNextRelevantToken(\Iterator $tokenIterator): void + { + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if (\T_WHITESPACE !== $t[0]) { + break; + } + } + } + + private function skipMethodArgument(\Iterator $tokenIterator): void + { + $openBraces = 0; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + + if ('[' === $t[0] || '(' === $t[0]) { + ++$openBraces; + } + + if (']' === $t[0] || ')' === $t[0]) { + --$openBraces; + } + + if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) { + break; + } + } + } + + /** + * Extracts the message from the iterator while the tokens + * match allowed message tokens. + */ + private function getValue(\Iterator $tokenIterator): string + { + $message = ''; + $docToken = ''; + $docPart = ''; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if ('.' === $t) { + // Concatenate with next token + continue; + } + if (!isset($t[1])) { + break; + } + + switch ($t[0]) { + case \T_START_HEREDOC: + $docToken = $t[1]; + break; + case \T_ENCAPSED_AND_WHITESPACE: + case \T_CONSTANT_ENCAPSED_STRING: + if ('' === $docToken) { + $message .= PhpStringTokenParser::parse($t[1]); + } else { + $docPart = $t[1]; + } + break; + case \T_END_HEREDOC: + if ($indentation = strspn($t[1], ' ')) { + $docPartWithLineBreaks = $docPart; + $docPart = ''; + + foreach (preg_split('~(\r\n|\n|\r)~', $docPartWithLineBreaks, -1, \PREG_SPLIT_DELIM_CAPTURE) as $str) { + if (\in_array($str, ["\r\n", "\n", "\r"], true)) { + $docPart .= $str; + } else { + $docPart .= substr($str, $indentation); + } + } + } + + $message .= PhpStringTokenParser::parseDocString($docToken, $docPart); + $docToken = ''; + $docPart = ''; + break; + case \T_WHITESPACE: + break; + default: + break 2; + } + } + + return $message; + } + + /** + * Extracts trans message from PHP tokens. + * + * @return void + */ + protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename) + { + $tokenIterator = new \ArrayIterator($tokens); + + for ($key = 0; $key < $tokenIterator->count(); ++$key) { + foreach ($this->sequences as $sequence) { + $message = ''; + $domain = 'messages'; + $tokenIterator->seek($key); + + foreach ($sequence as $sequenceKey => $item) { + $this->seekToNextRelevantToken($tokenIterator); + + if ($this->normalizeToken($tokenIterator->current()) === $item) { + $tokenIterator->next(); + continue; + } elseif (self::MESSAGE_TOKEN === $item) { + $message = $this->getValue($tokenIterator); + + if (\count($sequence) === ($sequenceKey + 1)) { + break; + } + } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) { + $this->skipMethodArgument($tokenIterator); + } elseif (self::DOMAIN_TOKEN === $item) { + $domainToken = $this->getValue($tokenIterator); + if ('' !== $domainToken) { + $domain = $domainToken; + } + + break; + } else { + break; + } + } + + if ($message) { + $catalog->set($message, $this->prefix.$message, $domain); + $metadata = $catalog->getMetadata($message, $domain) ?? []; + $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename); + $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2]; + $catalog->setMetadata($message, $metadata, $domain); + break; + } + } + } + } + + /** + * @throws \InvalidArgumentException + */ + protected function canBeExtracted(string $file): bool + { + return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION); + } + + protected function extractFromDirectory(string|array $directory): iterable + { + if (!class_exists(Finder::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class)); + } + + $finder = new Finder(); + + return $finder->files()->name('*.php')->in($directory); + } +} diff --git a/libraries/Symfony/Component/Translation/Extractor/PhpStringTokenParser.php b/libraries/Symfony/Component/Translation/Extractor/PhpStringTokenParser.php new file mode 100644 index 00000000000..cc13e0df353 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/PhpStringTokenParser.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor; + +trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated.', PhpStringTokenParser::class); + +/* + * The following is derived from code at http://github.com/nikic/PHP-Parser + * + * Copyright (c) 2011 by Nikita Popov + * + * Some rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * * The names of the contributors may not be used to endorse or + * promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @deprecated since Symfony 6.2 + */ +class PhpStringTokenParser +{ + protected static $replacements = [ + '\\' => '\\', + '$' => '$', + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'f' => "\f", + 'v' => "\v", + 'e' => "\x1B", + ]; + + /** + * Parses a string token. + * + * @param string $str String token content + */ + public static function parse(string $str): string + { + $bLength = 0; + if ('b' === $str[0]) { + $bLength = 1; + } + + if ('\'' === $str[$bLength]) { + return str_replace( + ['\\\\', '\\\''], + ['\\', '\''], + substr($str, $bLength + 1, -1) + ); + } else { + return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"'); + } + } + + /** + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param string|null $quote Quote type + */ + public static function parseEscapeSequences(string $str, ?string $quote = null): string + { + if (null !== $quote) { + $str = str_replace('\\'.$quote, $quote, $str); + } + + return preg_replace_callback( + '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~', + [__CLASS__, 'parseCallback'], + $str + ); + } + + private static function parseCallback(array $matches): string + { + $str = $matches[1]; + + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } elseif ('x' === $str[0] || 'X' === $str[0]) { + return \chr(hexdec($str)); + } else { + return \chr(octdec($str)); + } + } + + /** + * Parses a constant doc string. + * + * @param string $startToken Doc string start token content (<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor\Visitor; + +use PhpParser\Node; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * @author Mathieu Santostefano + */ +abstract class AbstractVisitor +{ + private MessageCatalogue $catalogue; + private \SplFileInfo $file; + private string $messagePrefix; + + public function initialize(MessageCatalogue $catalogue, \SplFileInfo $file, string $messagePrefix): void + { + $this->catalogue = $catalogue; + $this->file = $file; + $this->messagePrefix = $messagePrefix; + } + + protected function addMessageToCatalogue(string $message, ?string $domain, int $line): void + { + $domain ??= 'messages'; + $this->catalogue->set($message, $this->messagePrefix.$message, $domain); + $metadata = $this->catalogue->getMetadata($message, $domain) ?? []; + $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $this->file); + $metadata['sources'][] = $normalizedFilename.':'.$line; + $this->catalogue->setMetadata($message, $metadata, $domain); + } + + protected function getStringArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node, int|string $index, bool $indexIsRegex = false): array + { + if (\is_string($index)) { + return $this->getStringNamedArguments($node, $index, $indexIsRegex); + } + + $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args; + + if (!($arg = $args[$index] ?? null) instanceof Node\Arg) { + return []; + } + + return (array) $this->getStringValue($arg->value); + } + + protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): bool + { + $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args; + + foreach ($args as $arg) { + if ($arg instanceof Node\Arg && null !== $arg->name) { + return true; + } + } + + return false; + } + + protected function nodeFirstNamedArgumentIndex(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): int + { + $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args; + + foreach ($args as $i => $arg) { + if ($arg instanceof Node\Arg && null !== $arg->name) { + return $i; + } + } + + return \PHP_INT_MAX; + } + + private function getStringNamedArguments(Node\Expr\CallLike|Node\Attribute $node, ?string $argumentName = null, bool $isArgumentNamePattern = false): array + { + $args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args; + $argumentValues = []; + + foreach ($args as $arg) { + if (!$isArgumentNamePattern && $arg->name?->toString() === $argumentName) { + $argumentValues[] = $this->getStringValue($arg->value); + } elseif ($isArgumentNamePattern && preg_match($argumentName, $arg->name?->toString() ?? '') > 0) { + $argumentValues[] = $this->getStringValue($arg->value); + } + } + + return array_filter($argumentValues); + } + + private function getStringValue(Node $node): ?string + { + if ($node instanceof Node\Scalar\String_) { + return $node->value; + } + + if ($node instanceof Node\Expr\BinaryOp\Concat) { + if (null === $left = $this->getStringValue($node->left)) { + return null; + } + + if (null === $right = $this->getStringValue($node->right)) { + return null; + } + + return $left.$right; + } + + if ($node instanceof Node\Expr\Assign && $node->expr instanceof Node\Scalar\String_) { + return $node->expr->value; + } + + if ($node instanceof Node\Expr\ClassConstFetch) { + try { + $reflection = new \ReflectionClass($node->class->toString()); + $constant = $reflection->getReflectionConstant($node->name->toString()); + if (false !== $constant && \is_string($constant->getValue())) { + return $constant->getValue(); + } + } catch (\ReflectionException) { + } + } + + return null; + } +} diff --git a/libraries/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php b/libraries/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php new file mode 100644 index 00000000000..65de4448ec0 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor\Visitor; + +use PhpParser\Node; +use PhpParser\NodeVisitor; + +/** + * @author Mathieu Santostefano + * + * Code mostly comes from https://github.com/php-translation/extractor/blob/master/src/Visitor/Php/Symfony/Constraint.php + */ +final class ConstraintVisitor extends AbstractVisitor implements NodeVisitor +{ + public function __construct( + private readonly array $constraintClassNames = [] + ) { + } + + public function beforeTraverse(array $nodes): ?Node + { + return null; + } + + public function enterNode(Node $node): ?Node + { + return null; + } + + public function leaveNode(Node $node): ?Node + { + if (!$node instanceof Node\Expr\New_ && !$node instanceof Node\Attribute) { + return null; + } + + $className = $node instanceof Node\Attribute ? $node->name : $node->class; + if (!$className instanceof Node\Name) { + return null; + } + + $parts = $className->getParts(); + $isConstraintClass = false; + + foreach ($parts as $part) { + if (\in_array($part, $this->constraintClassNames, true)) { + $isConstraintClass = true; + + break; + } + } + + if (!$isConstraintClass) { + return null; + } + + $arg = $node->args[0] ?? null; + if (!$arg instanceof Node\Arg) { + return null; + } + + if ($this->hasNodeNamedArguments($node)) { + $messages = $this->getStringArguments($node, '/message/i', true); + } else { + if (!$arg->value instanceof Node\Expr\Array_) { + // There is no way to guess which argument is a message to be translated. + return null; + } + + $messages = []; + $options = $arg->value; + + /** @var Node\Expr\ArrayItem $item */ + foreach ($options->items as $item) { + if (!$item->key instanceof Node\Scalar\String_) { + continue; + } + + if (false === stripos($item->key->value ?? '', 'message')) { + continue; + } + + if (!$item->value instanceof Node\Scalar\String_) { + continue; + } + + $messages[] = $item->value->value; + + break; + } + } + + foreach ($messages as $message) { + $this->addMessageToCatalogue($message, 'validators', $node->getStartLine()); + } + + return null; + } + + public function afterTraverse(array $nodes): ?Node + { + return null; + } +} diff --git a/libraries/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php b/libraries/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php new file mode 100644 index 00000000000..93768db49da --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor\Visitor; + +use PhpParser\Node; +use PhpParser\NodeVisitor; + +/** + * @author Mathieu Santostefano + */ +final class TransMethodVisitor extends AbstractVisitor implements NodeVisitor +{ + public function beforeTraverse(array $nodes): ?Node + { + return null; + } + + public function enterNode(Node $node): ?Node + { + return null; + } + + public function leaveNode(Node $node): ?Node + { + if (!$node instanceof Node\Expr\MethodCall && !$node instanceof Node\Expr\FuncCall) { + return null; + } + + if (!\is_string($node->name) && !$node->name instanceof Node\Identifier && !$node->name instanceof Node\Name) { + return null; + } + + $name = $node->name instanceof Node\Name ? $node->name->getLast() : (string) $node->name; + + if ('trans' === $name || 't' === $name) { + $firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node); + + if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) { + return null; + } + + $domain = $this->getStringArguments($node, 2 < $firstNamedArgumentIndex ? 2 : 'domain')[0] ?? null; + + foreach ($messages as $message) { + $this->addMessageToCatalogue($message, $domain, $node->getStartLine()); + } + } + + return null; + } + + public function afterTraverse(array $nodes): ?Node + { + return null; + } +} diff --git a/libraries/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php b/libraries/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php new file mode 100644 index 00000000000..0e7bf60cce5 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Extractor\Visitor; + +use PhpParser\Node; +use PhpParser\NodeVisitor; + +/** + * @author Mathieu Santostefano + */ +final class TranslatableMessageVisitor extends AbstractVisitor implements NodeVisitor +{ + public function beforeTraverse(array $nodes): ?Node + { + return null; + } + + public function enterNode(Node $node): ?Node + { + return null; + } + + public function leaveNode(Node $node): ?Node + { + if (!$node instanceof Node\Expr\New_) { + return null; + } + + if (!($className = $node->class) instanceof Node\Name) { + return null; + } + + if (!\in_array('TranslatableMessage', $className->getParts(), true)) { + return null; + } + + $firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node); + + if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) { + return null; + } + + $domain = $this->getStringArguments($node, 2 < $firstNamedArgumentIndex ? 2 : 'domain')[0] ?? null; + + foreach ($messages as $message) { + $this->addMessageToCatalogue($message, $domain, $node->getStartLine()); + } + + return null; + } + + public function afterTraverse(array $nodes): ?Node + { + return null; + } +} diff --git a/libraries/Symfony/Component/Translation/Formatter/IntlFormatter.php b/libraries/Symfony/Component/Translation/Formatter/IntlFormatter.php new file mode 100644 index 00000000000..650787623cf --- /dev/null +++ b/libraries/Symfony/Component/Translation/Formatter/IntlFormatter.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Formatter; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\Exception\LogicException; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +class IntlFormatter implements IntlFormatterInterface +{ + private bool $hasMessageFormatter; + private array $cache = []; + + public function formatIntl(string $message, string $locale, array $parameters = []): string + { + // MessageFormatter constructor throws an exception if the message is empty + if ('' === $message) { + return ''; + } + + if (!$formatter = $this->cache[$locale][$message] ?? null) { + if (!$this->hasMessageFormatter ??= class_exists(\MessageFormatter::class)) { + throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.'); + } + try { + $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); + } catch (\IntlException $e) { + throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e); + } + } + + foreach ($parameters as $key => $value) { + if (\in_array($key[0] ?? null, ['%', '{'], true)) { + unset($parameters[$key]); + $parameters[trim($key, '%{ }')] = $value; + } + } + + if (false === $message = $formatter->format($parameters)) { + throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage()); + } + + return $message; + } +} diff --git a/libraries/Symfony/Component/Translation/Formatter/IntlFormatterInterface.php b/libraries/Symfony/Component/Translation/Formatter/IntlFormatterInterface.php new file mode 100644 index 00000000000..ff354ee47c0 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Formatter/IntlFormatterInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Formatter; + +/** + * Formats ICU message patterns. + * + * @author Nicolas Grekas + */ +interface IntlFormatterInterface +{ + /** + * Formats a localized message using rules defined by ICU MessageFormat. + * + * @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string; +} diff --git a/libraries/Symfony/Component/Translation/Formatter/MessageFormatter.php b/libraries/Symfony/Component/Translation/Formatter/MessageFormatter.php new file mode 100644 index 00000000000..86fbc8e7e61 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Formatter/MessageFormatter.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Formatter; + +use EDD\Vendor\Symfony\Component\Translation\IdentityTranslator; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(IntlFormatter::class); + +/** + * @author Abdellatif Ait boudad + */ +class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface +{ + private TranslatorInterface $translator; + private IntlFormatterInterface $intlFormatter; + + /** + * @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization + */ + public function __construct(?TranslatorInterface $translator = null, ?IntlFormatterInterface $intlFormatter = null) + { + $this->translator = $translator ?? new IdentityTranslator(); + $this->intlFormatter = $intlFormatter ?? new IntlFormatter(); + } + + public function format(string $message, string $locale, array $parameters = []): string + { + return $this->translator->trans($message, $parameters, null, $locale); + } + + public function formatIntl(string $message, string $locale, array $parameters = []): string + { + return $this->intlFormatter->formatIntl($message, $locale, $parameters); + } +} diff --git a/libraries/Symfony/Component/Translation/Formatter/MessageFormatterInterface.php b/libraries/Symfony/Component/Translation/Formatter/MessageFormatterInterface.php new file mode 100644 index 00000000000..ea9e0e0097a --- /dev/null +++ b/libraries/Symfony/Component/Translation/Formatter/MessageFormatterInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Formatter; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +interface MessageFormatterInterface +{ + /** + * Formats a localized message pattern with given arguments. + * + * @param string $message The message (may also be an object that can be cast to string) + * @param string $locale The message locale + * @param array $parameters An array of parameters for the message + */ + public function format(string $message, string $locale, array $parameters = []): string; +} diff --git a/libraries/Symfony/Component/Translation/IdentityTranslator.php b/libraries/Symfony/Component/Translation/IdentityTranslator.php new file mode 100644 index 00000000000..baef1346cd7 --- /dev/null +++ b/libraries/Symfony/Component/Translation/IdentityTranslator.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use EDD\Vendor\Symfony\Contracts\Translation\LocaleAwareInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorTrait; + +/** + * IdentityTranslator does not translate anything. + * + * @author Fabien Potencier + */ +class IdentityTranslator implements TranslatorInterface, LocaleAwareInterface +{ + use TranslatorTrait; +} diff --git a/libraries/Symfony/Component/Translation/LICENSE b/libraries/Symfony/Component/Translation/LICENSE new file mode 100644 index 00000000000..0138f8f0713 --- /dev/null +++ b/libraries/Symfony/Component/Translation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libraries/Symfony/Component/Translation/Loader/ArrayLoader.php b/libraries/Symfony/Component/Translation/Loader/ArrayLoader.php new file mode 100644 index 00000000000..b89308bdb2c --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/ArrayLoader.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * ArrayLoader loads translations from a PHP array. + * + * @author Fabien Potencier + */ +class ArrayLoader implements LoaderInterface +{ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + $resource = $this->flatten($resource); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($resource, $domain); + + return $catalogue; + } + + /** + * Flattens an nested array of translations. + * + * The scheme used is: + * 'key' => ['key2' => ['key3' => 'value']] + * Becomes: + * 'key.key2.key3' => 'value' + */ + private function flatten(array $messages): array + { + $result = []; + foreach ($messages as $key => $value) { + if (\is_array($value)) { + foreach ($this->flatten($value) as $k => $v) { + if (null !== $v) { + $result[$key.'.'.$k] = $v; + } + } + } elseif (null !== $value) { + $result[$key] = $value; + } + } + + return $result; + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/CsvFileLoader.php b/libraries/Symfony/Component/Translation/Loader/CsvFileLoader.php new file mode 100644 index 00000000000..56a33fc6a75 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/CsvFileLoader.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use EDD\Vendor\Symfony\Component\Translation\Exception\NotFoundResourceException; + +/** + * CsvFileLoader loads translations from CSV files. + * + * @author Saša Stamenković + */ +class CsvFileLoader extends FileLoader +{ + private string $delimiter = ';'; + private string $enclosure = '"'; + private string $escape = ''; + + protected function loadResource(string $resource): array + { + $messages = []; + + try { + $file = new \SplFileObject($resource, 'rb'); + } catch (\RuntimeException $e) { + throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e); + } + + $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY); + $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape); + + foreach ($file as $data) { + if (false === $data) { + continue; + } + + if (!str_starts_with($data[0], '#') && isset($data[1]) && 2 === \count($data)) { + $messages[$data[0]] = $data[1]; + } + } + + return $messages; + } + + /** + * Sets the delimiter, enclosure, and escape character for CSV. + * + * @return void + */ + public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + $this->escape = $escape; + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/FileLoader.php b/libraries/Symfony/Component/Translation/Loader/FileLoader.php new file mode 100644 index 00000000000..6600ee50447 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/FileLoader.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\NotFoundResourceException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * @author Abdellatif Ait boudad + */ +abstract class FileLoader extends ArrayLoader +{ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $messages = $this->loadResource($resource); + + // empty resource + $messages ??= []; + + // not an array + if (!\is_array($messages)) { + throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + } + + $catalogue = parent::load($messages, $locale, $domain); + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + + return $catalogue; + } + + /** + * @throws InvalidResourceException if stream content has an invalid format + */ + abstract protected function loadResource(string $resource): array; +} diff --git a/libraries/Symfony/Component/Translation/Loader/IcuDatFileLoader.php b/libraries/Symfony/Component/Translation/Loader/IcuDatFileLoader.php new file mode 100644 index 00000000000..cb31baa832f --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/IcuDatFileLoader.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\NotFoundResourceException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuDatFileLoader extends IcuResFileLoader +{ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + if (!stream_is_local($resource.'.dat')) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource.'.dat')) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $rb = new \ResourceBundle($locale, $resource); + } catch (\Exception) { + $rb = null; + } + + if (!$rb) { + throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); + } elseif (intl_is_failure($rb->getErrorCode())) { + throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + + $messages = $this->flatten($rb); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($messages, $domain); + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource.'.dat')); + } + + return $catalogue; + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/IcuResFileLoader.php b/libraries/Symfony/Component/Translation/Loader/IcuResFileLoader.php new file mode 100644 index 00000000000..a5bd4a78e29 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/IcuResFileLoader.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\DirectoryResource; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\NotFoundResourceException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuResFileLoader implements LoaderInterface +{ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!is_dir($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $rb = new \ResourceBundle($locale, $resource); + } catch (\Exception) { + $rb = null; + } + + if (!$rb) { + throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); + } elseif (intl_is_failure($rb->getErrorCode())) { + throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + + $messages = $this->flatten($rb); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($messages, $domain); + + if (class_exists(DirectoryResource::class)) { + $catalogue->addResource(new DirectoryResource($resource)); + } + + return $catalogue; + } + + /** + * Flattens an ResourceBundle. + * + * The scheme used is: + * key { key2 { key3 { "value" } } } + * Becomes: + * 'key.key2.key3' => 'value' + * + * This function takes an array by reference and will modify it + * + * @param \ResourceBundle $rb The ResourceBundle that will be flattened + * @param array $messages Used internally for recursive calls + * @param string|null $path Current path being parsed, used internally for recursive calls + */ + protected function flatten(\ResourceBundle $rb, array &$messages = [], ?string $path = null): array + { + foreach ($rb as $key => $value) { + $nodePath = $path ? $path.'.'.$key : $key; + if ($value instanceof \ResourceBundle) { + $this->flatten($value, $messages, $nodePath); + } else { + $messages[$nodePath] = $value; + } + } + + return $messages; + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/IniFileLoader.php b/libraries/Symfony/Component/Translation/Loader/IniFileLoader.php new file mode 100644 index 00000000000..07728aec4ce --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/IniFileLoader.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +/** + * IniFileLoader loads translations from an ini file. + * + * @author stealth35 + */ +class IniFileLoader extends FileLoader +{ + protected function loadResource(string $resource): array + { + return parse_ini_file($resource, true); + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/JsonFileLoader.php b/libraries/Symfony/Component/Translation/Loader/JsonFileLoader.php new file mode 100644 index 00000000000..3c5674f9964 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/JsonFileLoader.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * JsonFileLoader loads translations from an json file. + * + * @author singles + */ +class JsonFileLoader extends FileLoader +{ + protected function loadResource(string $resource): array + { + $messages = []; + if ($data = file_get_contents($resource)) { + $messages = json_decode($data, true); + + if (0 < $errorCode = json_last_error()) { + throw new InvalidResourceException('Error parsing JSON: '.$this->getJSONErrorMessage($errorCode)); + } + } + + return $messages; + } + + /** + * Translates JSON_ERROR_* constant into meaningful message. + */ + private function getJSONErrorMessage(int $errorCode): string + { + return match ($errorCode) { + \JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + \JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', + \JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + \JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + \JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + default => 'Unknown error', + }; + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/LoaderInterface.php b/libraries/Symfony/Component/Translation/Loader/LoaderInterface.php new file mode 100644 index 00000000000..db7fd440297 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/LoaderInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\NotFoundResourceException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * LoaderInterface is the interface implemented by all translation loaders. + * + * @author Fabien Potencier + */ +interface LoaderInterface +{ + /** + * Loads a locale. + * + * @throws NotFoundResourceException when the resource cannot be found + * @throws InvalidResourceException when the resource cannot be loaded + */ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue; +} diff --git a/libraries/Symfony/Component/Translation/Loader/MoFileLoader.php b/libraries/Symfony/Component/Translation/Loader/MoFileLoader.php new file mode 100644 index 00000000000..45c8acaba7b --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/MoFileLoader.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) + */ +class MoFileLoader extends FileLoader +{ + /** + * Magic used for validating the format of an MO file as well as + * detecting if the machine used to create that file was little endian. + */ + public const MO_LITTLE_ENDIAN_MAGIC = 0x950412DE; + + /** + * Magic used for validating the format of an MO file as well as + * detecting if the machine used to create that file was big endian. + */ + public const MO_BIG_ENDIAN_MAGIC = 0xDE120495; + + /** + * The size of the header of an MO file in bytes. + */ + public const MO_HEADER_SIZE = 28; + + /** + * Parses machine object (MO) format, independent of the machine's endian it + * was created on. Both 32bit and 64bit systems are supported. + */ + protected function loadResource(string $resource): array + { + $stream = fopen($resource, 'r'); + + $stat = fstat($stream); + + if ($stat['size'] < self::MO_HEADER_SIZE) { + throw new InvalidResourceException('MO stream content has an invalid format.'); + } + $magic = unpack('V1', fread($stream, 4)); + $magic = hexdec(substr(dechex(current($magic)), -8)); + + if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) { + $isBigEndian = false; + } elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) { + $isBigEndian = true; + } else { + throw new InvalidResourceException('MO stream content has an invalid format.'); + } + + // formatRevision + $this->readLong($stream, $isBigEndian); + $count = $this->readLong($stream, $isBigEndian); + $offsetId = $this->readLong($stream, $isBigEndian); + $offsetTranslated = $this->readLong($stream, $isBigEndian); + // sizeHashes + $this->readLong($stream, $isBigEndian); + // offsetHashes + $this->readLong($stream, $isBigEndian); + + $messages = []; + + for ($i = 0; $i < $count; ++$i) { + $pluralId = null; + $translated = null; + + fseek($stream, $offsetId + $i * 8); + + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $singularId = fread($stream, $length); + + if (str_contains($singularId, "\000")) { + [$singularId, $pluralId] = explode("\000", $singularId); + } + + fseek($stream, $offsetTranslated + $i * 8); + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $translated = fread($stream, $length); + + if (str_contains($translated, "\000")) { + $translated = explode("\000", $translated); + } + + $ids = ['singular' => $singularId, 'plural' => $pluralId]; + $item = compact('ids', 'translated'); + + if (!empty($item['ids']['singular'])) { + $id = $item['ids']['singular']; + if (isset($item['ids']['plural'])) { + $id .= '|'.$item['ids']['plural']; + } + $messages[$id] = stripcslashes(implode('|', (array) $item['translated'])); + } + } + + fclose($stream); + + return array_filter($messages); + } + + /** + * Reads an unsigned long from stream respecting endianness. + * + * @param resource $stream + */ + private function readLong($stream, bool $isBigEndian): int + { + $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); + $result = current($result); + + return (int) substr($result, -8); + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/PhpFileLoader.php b/libraries/Symfony/Component/Translation/Loader/PhpFileLoader.php new file mode 100644 index 00000000000..ad83b98b042 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/PhpFileLoader.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +/** + * PhpFileLoader loads translations from PHP files returning an array of translations. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + private static ?array $cache = []; + + protected function loadResource(string $resource): array + { + if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL))) { + self::$cache = null; + } + + if (null === self::$cache) { + return require $resource; + } + + return self::$cache[$resource] ??= require $resource; + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/PoFileLoader.php b/libraries/Symfony/Component/Translation/Loader/PoFileLoader.php new file mode 100644 index 00000000000..b992800f6f7 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/PoFileLoader.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +/** + * @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium + * @copyright Copyright (c) 2012, Clemens Tolboom + */ +class PoFileLoader extends FileLoader +{ + /** + * Parses portable object (PO) format. + * + * From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files + * we should be able to parse files having: + * + * white-space + * # translator-comments + * #. extracted-comments + * #: reference... + * #, flag... + * #| msgid previous-untranslated-string + * msgid untranslated-string + * msgstr translated-string + * + * extra or different lines are: + * + * #| msgctxt previous-context + * #| msgid previous-untranslated-string + * msgctxt context + * + * #| msgid previous-untranslated-string-singular + * #| msgid_plural previous-untranslated-string-plural + * msgid untranslated-string-singular + * msgid_plural untranslated-string-plural + * msgstr[0] translated-string-case-0 + * ... + * msgstr[N] translated-string-case-n + * + * The definition states: + * - white-space and comments are optional. + * - msgid "" that an empty singleline defines a header. + * + * This parser sacrifices some features of the reference implementation the + * differences to that implementation are as follows. + * - No support for comments spanning multiple lines. + * - Translator and extracted comments are treated as being the same type. + * - Message IDs are allowed to have other encodings as just US-ASCII. + * + * Items with an empty id are ignored. + */ + protected function loadResource(string $resource): array + { + $stream = fopen($resource, 'r'); + + $defaults = [ + 'ids' => [], + 'translated' => null, + ]; + + $messages = []; + $item = $defaults; + $flags = []; + + while ($line = fgets($stream)) { + $line = trim($line); + + if ('' === $line) { + // Whitespace indicated current item is done + if (!\in_array('fuzzy', $flags)) { + $this->addMessage($messages, $item); + } + $item = $defaults; + $flags = []; + } elseif (str_starts_with($line, '#,')) { + $flags = array_map('trim', explode(',', substr($line, 2))); + } elseif (str_starts_with($line, 'msgid "')) { + // We start a new msg so save previous + // TODO: this fails when comments or contexts are added + $this->addMessage($messages, $item); + $item = $defaults; + $item['ids']['singular'] = substr($line, 7, -1); + } elseif (str_starts_with($line, 'msgstr "')) { + $item['translated'] = substr($line, 8, -1); + } elseif ('"' === $line[0]) { + $continues = isset($item['translated']) ? 'translated' : 'ids'; + + if (\is_array($item[$continues])) { + end($item[$continues]); + $item[$continues][key($item[$continues])] .= substr($line, 1, -1); + } else { + $item[$continues] .= substr($line, 1, -1); + } + } elseif (str_starts_with($line, 'msgid_plural "')) { + $item['ids']['plural'] = substr($line, 14, -1); + } elseif (str_starts_with($line, 'msgstr[')) { + $size = strpos($line, ']'); + $item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1); + } + } + // save last item + if (!\in_array('fuzzy', $flags)) { + $this->addMessage($messages, $item); + } + fclose($stream); + + return $messages; + } + + /** + * Save a translation item to the messages. + * + * A .po file could contain by error missing plural indexes. We need to + * fix these before saving them. + */ + private function addMessage(array &$messages, array $item): void + { + if (!empty($item['ids']['singular'])) { + $id = stripcslashes($item['ids']['singular']); + if (isset($item['ids']['plural'])) { + $id .= '|'.stripcslashes($item['ids']['plural']); + } + + $translated = (array) $item['translated']; + // PO are by definition indexed so sort by index. + ksort($translated); + // Make sure every index is filled. + end($translated); + $count = key($translated); + // Fill missing spots with '-'. + $empties = array_fill(0, $count + 1, '-'); + $translated += $empties; + ksort($translated); + + $messages[$id] = stripcslashes(implode('|', $translated)); + } + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/QtFileLoader.php b/libraries/Symfony/Component/Translation/Loader/QtFileLoader.php new file mode 100644 index 00000000000..3740ac441fc --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/QtFileLoader.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\XmlUtils; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\NotFoundResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\RuntimeException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * QtFileLoader loads translations from QT Translations XML files. + * + * @author Benjamin Eberlei + */ +class QtFileLoader implements LoaderInterface +{ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + if (!class_exists(XmlUtils::class)) { + throw new RuntimeException('Loading translations from the QT format requires the Symfony Config component.'); + } + + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $dom = XmlUtils::loadFile($resource); + } catch (\InvalidArgumentException $e) { + throw new InvalidResourceException(sprintf('Unable to load "%s".', $resource), $e->getCode(), $e); + } + + $internalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + + $xpath = new \DOMXPath($dom); + $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]'); + + $catalogue = new MessageCatalogue($locale); + if (1 == $nodes->length) { + $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message'); + foreach ($translations as $translation) { + $translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue; + + if (!empty($translationValue)) { + $catalogue->set( + (string) $translation->getElementsByTagName('source')->item(0)->nodeValue, + $translationValue, + $domain + ); + } + } + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + } + + libxml_use_internal_errors($internalErrors); + + return $catalogue; + } +} diff --git a/libraries/Symfony/Component/Translation/Loader/XliffFileLoader.php b/libraries/Symfony/Component/Translation/Loader/XliffFileLoader.php new file mode 100644 index 00000000000..8a04733aa70 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\Exception\InvalidXmlException; +use Symfony\Component\Config\Util\Exception\XmlParsingException; +use Symfony\Component\Config\Util\XmlUtils; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\NotFoundResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\RuntimeException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; +use EDD\Vendor\Symfony\Component\Translation\Util\XliffUtils; + +/** + * XliffFileLoader loads translations from XLIFF files. + * + * @author Fabien Potencier + */ +class XliffFileLoader implements LoaderInterface +{ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + if (!class_exists(XmlUtils::class)) { + throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.'); + } + + if (!$this->isXmlString($resource)) { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + if (!is_file($resource)) { + throw new InvalidResourceException(sprintf('This is neither a file nor an XLIFF string "%s".', $resource)); + } + } + + try { + if ($this->isXmlString($resource)) { + $dom = XmlUtils::parse($resource); + } else { + $dom = XmlUtils::loadFile($resource); + } + } catch (\InvalidArgumentException|XmlParsingException|InvalidXmlException $e) { + throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e); + } + + if ($errors = XliffUtils::validateSchema($dom)) { + throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); + } + + $catalogue = new MessageCatalogue($locale); + $this->extract($dom, $catalogue, $domain); + + if (is_file($resource) && class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + + return $catalogue; + } + + private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xliffVersion = XliffUtils::getVersionNumber($dom); + + if ('1.2' === $xliffVersion) { + $this->extractXliff1($dom, $catalogue, $domain); + } + + if ('2.0' === $xliffVersion) { + $this->extractXliff2($dom, $catalogue, $domain); + } + } + + /** + * Extract messages and metadata from DOMDocument into a MessageCatalogue. + */ + private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $namespace = 'urn:oasis:names:tc:xliff:document:1.2'; + $xml->registerXPathNamespace('xliff', $namespace); + + foreach ($xml->xpath('//xliff:file') as $file) { + $fileAttributes = $file->attributes(); + + $file->registerXPathNamespace('xliff', $namespace); + + foreach ($file->xpath('.//xliff:prop') as $prop) { + $catalogue->setCatalogueMetadata($prop->attributes()['prop-type'], (string) $prop, $domain); + } + + foreach ($file->xpath('.//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); + + if (!(isset($attributes['resname']) || isset($translation->source))) { + continue; + } + + $source = (string) (isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source); + + if (isset($translation->target) + && 'needs-translation' === (string) $translation->target->attributes()['state'] + && \in_array((string) $translation->target, [$source, (string) $translation->source], true) + ) { + continue; + } + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); + + $catalogue->set($source, $target, $domain); + + $metadata = [ + 'source' => (string) $translation->source, + 'file' => [ + 'original' => (string) $fileAttributes['original'], + ], + ]; + if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { + $metadata['notes'] = $notes; + } + + if (isset($translation->target) && $translation->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($translation->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($attributes['id'])) { + $metadata['id'] = (string) $attributes['id']; + } + + $catalogue->setMetadata($source, $metadata, $domain); + } + } + } + + private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); + + foreach ($xml->xpath('//xliff:unit') as $unit) { + foreach ($unit->segment as $segment) { + $attributes = $unit->attributes(); + $source = $attributes['name'] ?? $segment->source; + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($segment->target ?? $segment->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = []; + if (isset($segment->target) && $segment->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($segment->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($unit->notes)) { + $metadata['notes'] = []; + foreach ($unit->notes->note as $noteNode) { + $note = []; + foreach ($noteNode->attributes() as $key => $value) { + $note[$key] = (string) $value; + } + $note['content'] = (string) $noteNode; + $metadata['notes'][] = $note; + } + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + + /** + * Convert a UTF8 string to the specified encoding. + */ + private function utf8ToCharset(string $content, ?string $encoding = null): string + { + if ('UTF-8' !== $encoding && !empty($encoding)) { + return mb_convert_encoding($content, $encoding, 'UTF-8'); + } + + return $content; + } + + private function parseNotesMetadata(?\SimpleXMLElement $noteElement = null, ?string $encoding = null): array + { + $notes = []; + + if (null === $noteElement) { + return $notes; + } + + /** @var \SimpleXMLElement $xmlNote */ + foreach ($noteElement as $xmlNote) { + $noteAttributes = $xmlNote->attributes(); + $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)]; + if (isset($noteAttributes['priority'])) { + $note['priority'] = (int) $noteAttributes['priority']; + } + + if (isset($noteAttributes['from'])) { + $note['from'] = (string) $noteAttributes['from']; + } + + $notes[] = $note; + } + + return $notes; + } + + private function isXmlString(string $resource): bool + { + return str_starts_with($resource, ' + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Loader; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileLoader loads translations from Yaml files. + * + * @author Fabien Potencier + */ +class YamlFileLoader extends FileLoader +{ + private YamlParser $yamlParser; + + protected function loadResource(string $resource): array + { + if (!isset($this->yamlParser)) { + if (!class_exists(\Symfony\Component\Yaml\Parser::class)) { + throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.'); + } + + $this->yamlParser = new YamlParser(); + } + + try { + $messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT); + } catch (ParseException $e) { + throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e); + } + + if (null !== $messages && !\is_array($messages)) { + throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + } + + return $messages ?: []; + } +} diff --git a/libraries/Symfony/Component/Translation/LocaleSwitcher.php b/libraries/Symfony/Component/Translation/LocaleSwitcher.php new file mode 100644 index 00000000000..c72144b919c --- /dev/null +++ b/libraries/Symfony/Component/Translation/LocaleSwitcher.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use Symfony\Component\Routing\RequestContext; +use EDD\Vendor\Symfony\Contracts\Translation\LocaleAwareInterface; + +/** + * @author Kevin Bond + */ +class LocaleSwitcher implements LocaleAwareInterface +{ + private string $defaultLocale; + + /** + * @param LocaleAwareInterface[] $localeAwareServices + */ + public function __construct( + private string $locale, + private iterable $localeAwareServices, + private ?RequestContext $requestContext = null, + ) { + $this->defaultLocale = $locale; + } + + public function setLocale(string $locale): void + { + // Silently ignore if the intl extension is not loaded + try { + if (class_exists(\Locale::class, false)) { + \Locale::setDefault($locale); + } + } catch (\Exception) { + } + + $this->locale = $locale; + $this->requestContext?->setParameter('_locale', $locale); + + foreach ($this->localeAwareServices as $service) { + $service->setLocale($locale); + } + } + + public function getLocale(): string + { + return $this->locale; + } + + /** + * Switch to a new locale, execute a callback, then switch back to the original. + * + * @template T + * + * @param callable(string $locale):T $callback + * + * @return T + */ + public function runWithLocale(string $locale, callable $callback): mixed + { + $original = $this->getLocale(); + $this->setLocale($locale); + + try { + return $callback($locale); + } finally { + $this->setLocale($original); + } + } + + public function reset(): void + { + $this->setLocale($this->defaultLocale); + } +} diff --git a/libraries/Symfony/Component/Translation/LoggingTranslator.php b/libraries/Symfony/Component/Translation/LoggingTranslator.php new file mode 100644 index 00000000000..6fd2bee9983 --- /dev/null +++ b/libraries/Symfony/Component/Translation/LoggingTranslator.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use Psr\Log\LoggerInterface; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Contracts\Translation\LocaleAwareInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Abdellatif Ait boudad + */ +class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface +{ + private TranslatorInterface $translator; + private LoggerInterface $logger; + + /** + * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator The translator must implement TranslatorBagInterface + */ + public function __construct(TranslatorInterface $translator, LoggerInterface $logger) + { + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator))); + } + + $this->translator = $translator; + $this->logger = $logger; + } + + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale); + $this->log($id, $domain, $locale); + + return $trans; + } + + /** + * @return void + */ + public function setLocale(string $locale) + { + $prev = $this->translator->getLocale(); + $this->translator->setLocale($locale); + if ($prev === $locale) { + return; + } + + $this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale)); + } + + public function getLocale(): string + { + return $this->translator->getLocale(); + } + + public function getCatalogue(?string $locale = null): MessageCatalogueInterface + { + return $this->translator->getCatalogue($locale); + } + + public function getCatalogues(): array + { + return $this->translator->getCatalogues(); + } + + /** + * Gets the fallback locales. + */ + public function getFallbackLocales(): array + { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { + return $this->translator->getFallbackLocales(); + } + + return []; + } + + /** + * @return mixed + */ + public function __call(string $method, array $args) + { + return $this->translator->{$method}(...$args); + } + + /** + * Logs for missing translations. + */ + private function log(string $id, ?string $domain, ?string $locale): void + { + $domain ??= 'messages'; + + $catalogue = $this->translator->getCatalogue($locale); + if ($catalogue->defines($id, $domain)) { + return; + } + + if ($catalogue->has($id, $domain)) { + $this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); + } else { + $this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); + } + } +} diff --git a/libraries/Symfony/Component/Translation/MessageCatalogue.php b/libraries/Symfony/Component/Translation/MessageCatalogue.php new file mode 100644 index 00000000000..31bde032214 --- /dev/null +++ b/libraries/Symfony/Component/Translation/MessageCatalogue.php @@ -0,0 +1,338 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use Symfony\Component\Config\Resource\ResourceInterface; +use EDD\Vendor\Symfony\Component\Translation\Exception\LogicException; + +/** + * @author Fabien Potencier + */ +class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface, CatalogueMetadataAwareInterface +{ + private array $messages = []; + private array $metadata = []; + private array $catalogueMetadata = []; + private array $resources = []; + private string $locale; + private ?MessageCatalogueInterface $fallbackCatalogue = null; + private ?self $parent = null; + + /** + * @param array $messages An array of messages classified by domain + */ + public function __construct(string $locale, array $messages = []) + { + $this->locale = $locale; + $this->messages = $messages; + } + + public function getLocale(): string + { + return $this->locale; + } + + public function getDomains(): array + { + $domains = []; + + foreach ($this->messages as $domain => $messages) { + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)); + } + $domains[$domain] = $domain; + } + + return array_values($domains); + } + + public function all(?string $domain = null): array + { + if (null !== $domain) { + // skip messages merge if intl-icu requested explicitly + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + return $this->messages[$domain] ?? []; + } + + return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []); + } + + $allMessages = []; + + foreach ($this->messages as $domain => $messages) { + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)); + $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []); + } else { + $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages; + } + } + + return $allMessages; + } + + /** + * @return void + */ + public function set(string $id, string $translation, string $domain = 'messages') + { + $this->add([$id => $translation], $domain); + } + + public function has(string $id, string $domain = 'messages'): bool + { + if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { + return true; + } + + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->has($id, $domain); + } + + return false; + } + + public function defines(string $id, string $domain = 'messages'): bool + { + return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]); + } + + public function get(string $id, string $domain = 'messages'): string + { + if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { + return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]; + } + + if (isset($this->messages[$domain][$id])) { + return $this->messages[$domain][$id]; + } + + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->get($id, $domain); + } + + return $id; + } + + /** + * @return void + */ + public function replace(array $messages, string $domain = 'messages') + { + unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]); + + $this->add($messages, $domain); + } + + /** + * @return void + */ + public function add(array $messages, string $domain = 'messages') + { + $altDomain = str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain.self::INTL_DOMAIN_SUFFIX; + foreach ($messages as $id => $message) { + unset($this->messages[$altDomain][$id]); + $this->messages[$domain][$id] = $message; + } + + if ([] === ($this->messages[$altDomain] ?? null)) { + unset($this->messages[$altDomain]); + } + } + + /** + * @return void + */ + public function addCatalogue(MessageCatalogueInterface $catalogue) + { + if ($catalogue->getLocale() !== $this->locale) { + throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale)); + } + + foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) { + $this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX); + $messages = array_diff_key($messages, $intlMessages); + } + $this->add($messages, $domain); + } + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + + if ($catalogue instanceof MetadataAwareInterface) { + $metadata = $catalogue->getMetadata('', ''); + $this->addMetadata($metadata); + } + + if ($catalogue instanceof CatalogueMetadataAwareInterface) { + $catalogueMetadata = $catalogue->getCatalogueMetadata('', ''); + $this->addCatalogueMetadata($catalogueMetadata); + } + } + + /** + * @return void + */ + public function addFallbackCatalogue(MessageCatalogueInterface $catalogue) + { + // detect circular references + $c = $catalogue; + while ($c = $c->getFallbackCatalogue()) { + if ($c->getLocale() === $this->getLocale()) { + throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + } + + $c = $this; + do { + if ($c->getLocale() === $catalogue->getLocale()) { + throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + + foreach ($catalogue->getResources() as $resource) { + $c->addResource($resource); + } + } while ($c = $c->parent); + + $catalogue->parent = $this; + $this->fallbackCatalogue = $catalogue; + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + } + + public function getFallbackCatalogue(): ?MessageCatalogueInterface + { + return $this->fallbackCatalogue; + } + + public function getResources(): array + { + return array_values($this->resources); + } + + /** + * @return void + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[$resource->__toString()] = $resource; + } + + public function getMetadata(string $key = '', string $domain = 'messages'): mixed + { + if ('' == $domain) { + return $this->metadata; + } + + if (isset($this->metadata[$domain])) { + if ('' == $key) { + return $this->metadata[$domain]; + } + + if (isset($this->metadata[$domain][$key])) { + return $this->metadata[$domain][$key]; + } + } + + return null; + } + + /** + * @return void + */ + public function setMetadata(string $key, mixed $value, string $domain = 'messages') + { + $this->metadata[$domain][$key] = $value; + } + + /** + * @return void + */ + public function deleteMetadata(string $key = '', string $domain = 'messages') + { + if ('' == $domain) { + $this->metadata = []; + } elseif ('' == $key) { + unset($this->metadata[$domain]); + } else { + unset($this->metadata[$domain][$key]); + } + } + + public function getCatalogueMetadata(string $key = '', string $domain = 'messages'): mixed + { + if (!$domain) { + return $this->catalogueMetadata; + } + + if (isset($this->catalogueMetadata[$domain])) { + if (!$key) { + return $this->catalogueMetadata[$domain]; + } + + if (isset($this->catalogueMetadata[$domain][$key])) { + return $this->catalogueMetadata[$domain][$key]; + } + } + + return null; + } + + /** + * @return void + */ + public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages') + { + $this->catalogueMetadata[$domain][$key] = $value; + } + + /** + * @return void + */ + public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages') + { + if (!$domain) { + $this->catalogueMetadata = []; + } elseif (!$key) { + unset($this->catalogueMetadata[$domain]); + } else { + unset($this->catalogueMetadata[$domain][$key]); + } + } + + /** + * Adds current values with the new values. + * + * @param array $values Values to add + */ + private function addMetadata(array $values): void + { + foreach ($values as $domain => $keys) { + foreach ($keys as $key => $value) { + $this->setMetadata($key, $value, $domain); + } + } + } + + private function addCatalogueMetadata(array $values): void + { + foreach ($values as $domain => $keys) { + foreach ($keys as $key => $value) { + $this->setCatalogueMetadata($key, $value, $domain); + } + } + } +} diff --git a/libraries/Symfony/Component/Translation/MessageCatalogueInterface.php b/libraries/Symfony/Component/Translation/MessageCatalogueInterface.php new file mode 100644 index 00000000000..8bbd7d258bc --- /dev/null +++ b/libraries/Symfony/Component/Translation/MessageCatalogueInterface.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * MessageCatalogueInterface. + * + * @author Fabien Potencier + */ +interface MessageCatalogueInterface +{ + public const INTL_DOMAIN_SUFFIX = '+intl-icu'; + + /** + * Gets the catalogue locale. + */ + public function getLocale(): string; + + /** + * Gets the domains. + */ + public function getDomains(): array; + + /** + * Gets the messages within a given domain. + * + * If $domain is null, it returns all messages. + */ + public function all(?string $domain = null): array; + + /** + * Sets a message translation. + * + * @param string $id The message id + * @param string $translation The messages translation + * @param string $domain The domain name + * + * @return void + */ + public function set(string $id, string $translation, string $domain = 'messages'); + + /** + * Checks if a message has a translation. + * + * @param string $id The message id + * @param string $domain The domain name + */ + public function has(string $id, string $domain = 'messages'): bool; + + /** + * Checks if a message has a translation (it does not take into account the fallback mechanism). + * + * @param string $id The message id + * @param string $domain The domain name + */ + public function defines(string $id, string $domain = 'messages'): bool; + + /** + * Gets a message translation. + * + * @param string $id The message id + * @param string $domain The domain name + */ + public function get(string $id, string $domain = 'messages'): string; + + /** + * Sets translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + * + * @return void + */ + public function replace(array $messages, string $domain = 'messages'); + + /** + * Adds translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + * + * @return void + */ + public function add(array $messages, string $domain = 'messages'); + + /** + * Merges translations from the given Catalogue into the current one. + * + * The two catalogues must have the same locale. + * + * @return void + */ + public function addCatalogue(self $catalogue); + + /** + * Merges translations from the given Catalogue into the current one + * only when the translation does not exist. + * + * This is used to provide default translations when they do not exist for the current locale. + * + * @return void + */ + public function addFallbackCatalogue(self $catalogue); + + /** + * Gets the fallback catalogue. + */ + public function getFallbackCatalogue(): ?self; + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] + */ + public function getResources(): array; + + /** + * Adds a resource for this collection. + * + * @return void + */ + public function addResource(ResourceInterface $resource); +} diff --git a/libraries/Symfony/Component/Translation/MetadataAwareInterface.php b/libraries/Symfony/Component/Translation/MetadataAwareInterface.php new file mode 100644 index 00000000000..9052f1f1967 --- /dev/null +++ b/libraries/Symfony/Component/Translation/MetadataAwareInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +/** + * This interface is used to get, set, and delete metadata about the translation messages. + * + * @author Fabien Potencier + */ +interface MetadataAwareInterface +{ + /** + * Gets metadata for the given domain and key. + * + * Passing an empty domain will return an array with all metadata indexed by + * domain and then by key. Passing an empty key will return an array with all + * metadata for the given domain. + * + * @return mixed The value that was set or an array with the domains/keys or null + */ + public function getMetadata(string $key = '', string $domain = 'messages'): mixed; + + /** + * Adds metadata to a message domain. + * + * @return void + */ + public function setMetadata(string $key, mixed $value, string $domain = 'messages'); + + /** + * Deletes metadata for the given key and domain. + * + * Passing an empty domain will delete all metadata. Passing an empty key will + * delete all metadata for the given domain. + * + * @return void + */ + public function deleteMetadata(string $key = '', string $domain = 'messages'); +} diff --git a/libraries/Symfony/Component/Translation/Provider/AbstractProviderFactory.php b/libraries/Symfony/Component/Translation/Provider/AbstractProviderFactory.php new file mode 100644 index 00000000000..d3e800078e4 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/AbstractProviderFactory.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\Exception\IncompleteDsnException; + +abstract class AbstractProviderFactory implements ProviderFactoryInterface +{ + public function supports(Dsn $dsn): bool + { + return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), true); + } + + /** + * @return string[] + */ + abstract protected function getSupportedSchemes(): array; + + protected function getUser(Dsn $dsn): string + { + return $dsn->getUser() ?? throw new IncompleteDsnException('User is not set.', $dsn->getScheme().'://'.$dsn->getHost()); + } + + protected function getPassword(Dsn $dsn): string + { + return $dsn->getPassword() ?? throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn()); + } +} diff --git a/libraries/Symfony/Component/Translation/Provider/Dsn.php b/libraries/Symfony/Component/Translation/Provider/Dsn.php new file mode 100644 index 00000000000..4d5ef0fe9e7 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/Dsn.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\Exception\MissingRequiredOptionException; + +/** + * @author Fabien Potencier + * @author Oskar Stark + */ +final class Dsn +{ + private ?string $scheme; + private ?string $host; + private ?string $user; + private ?string $password; + private ?int $port; + private ?string $path; + private array $options = []; + private string $originalDsn; + + public function __construct(#[\SensitiveParameter] string $dsn) + { + $this->originalDsn = $dsn; + + if (false === $params = parse_url($dsn)) { + throw new InvalidArgumentException('The translation provider DSN is invalid.'); + } + + if (!isset($params['scheme'])) { + throw new InvalidArgumentException('The translation provider DSN must contain a scheme.'); + } + $this->scheme = $params['scheme']; + + if (!isset($params['host'])) { + throw new InvalidArgumentException('The translation provider DSN must contain a host (use "default" by default).'); + } + $this->host = $params['host']; + + $this->user = '' !== ($params['user'] ?? '') ? rawurldecode($params['user']) : null; + $this->password = '' !== ($params['pass'] ?? '') ? rawurldecode($params['pass']) : null; + $this->port = $params['port'] ?? null; + $this->path = $params['path'] ?? null; + parse_str($params['query'] ?? '', $this->options); + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getHost(): string + { + return $this->host; + } + + public function getUser(): ?string + { + return $this->user; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function getPort(?int $default = null): ?int + { + return $this->port ?? $default; + } + + public function getOption(string $key, mixed $default = null): mixed + { + return $this->options[$key] ?? $default; + } + + public function getRequiredOption(string $key): mixed + { + if (!\array_key_exists($key, $this->options) || '' === trim($this->options[$key])) { + throw new MissingRequiredOptionException($key); + } + + return $this->options[$key]; + } + + public function getOptions(): array + { + return $this->options; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function getOriginalDsn(): string + { + return $this->originalDsn; + } +} diff --git a/libraries/Symfony/Component/Translation/Provider/FilteringProvider.php b/libraries/Symfony/Component/Translation/Provider/FilteringProvider.php new file mode 100644 index 00000000000..3d00c8f4bfb --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/FilteringProvider.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\TranslatorBag; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBagInterface; + +/** + * Filters domains and locales between the Translator config values and those specific to each provider. + * + * @author Mathieu Santostefano + */ +class FilteringProvider implements ProviderInterface +{ + private ProviderInterface $provider; + private array $locales; + private array $domains; + + public function __construct(ProviderInterface $provider, array $locales, array $domains = []) + { + $this->provider = $provider; + $this->locales = $locales; + $this->domains = $domains; + } + + public function __toString(): string + { + return (string) $this->provider; + } + + public function write(TranslatorBagInterface $translatorBag): void + { + $this->provider->write($translatorBag); + } + + public function read(array $domains, array $locales): TranslatorBag + { + $domains = !$this->domains ? $domains : array_intersect($this->domains, $domains); + $locales = array_intersect($this->locales, $locales); + + return $this->provider->read($domains, $locales); + } + + public function delete(TranslatorBagInterface $translatorBag): void + { + $this->provider->delete($translatorBag); + } + + public function getDomains(): array + { + return $this->domains; + } +} diff --git a/libraries/Symfony/Component/Translation/Provider/NullProvider.php b/libraries/Symfony/Component/Translation/Provider/NullProvider.php new file mode 100644 index 00000000000..4844ae587bf --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/NullProvider.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\TranslatorBag; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBagInterface; + +/** + * @author Mathieu Santostefano + */ +class NullProvider implements ProviderInterface +{ + public function __toString(): string + { + return 'null'; + } + + public function write(TranslatorBagInterface $translatorBag, bool $override = false): void + { + } + + public function read(array $domains, array $locales): TranslatorBag + { + return new TranslatorBag(); + } + + public function delete(TranslatorBagInterface $translatorBag): void + { + } +} diff --git a/libraries/Symfony/Component/Translation/Provider/NullProviderFactory.php b/libraries/Symfony/Component/Translation/Provider/NullProviderFactory.php new file mode 100644 index 00000000000..92078af9f36 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/NullProviderFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +/** + * @author Mathieu Santostefano + */ +final class NullProviderFactory extends AbstractProviderFactory +{ + public function create(Dsn $dsn): ProviderInterface + { + if ('null' === $dsn->getScheme()) { + return new NullProvider(); + } + + throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes()); + } + + protected function getSupportedSchemes(): array + { + return ['null']; + } +} diff --git a/libraries/Symfony/Component/Translation/Provider/ProviderFactoryInterface.php b/libraries/Symfony/Component/Translation/Provider/ProviderFactoryInterface.php new file mode 100644 index 00000000000..4c6eec1113d --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/ProviderFactoryInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\Exception\IncompleteDsnException; +use EDD\Vendor\Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +interface ProviderFactoryInterface +{ + /** + * @throws UnsupportedSchemeException + * @throws IncompleteDsnException + */ + public function create(Dsn $dsn): ProviderInterface; + + public function supports(Dsn $dsn): bool; +} diff --git a/libraries/Symfony/Component/Translation/Provider/ProviderInterface.php b/libraries/Symfony/Component/Translation/Provider/ProviderInterface.php new file mode 100644 index 00000000000..8348f194502 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/ProviderInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\TranslatorBag; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBagInterface; + +interface ProviderInterface extends \Stringable +{ + /** + * Translations available in the TranslatorBag only must be created. + * Translations available in both the TranslatorBag and on the provider + * must be overwritten. + * Translations available on the provider only must be kept. + */ + public function write(TranslatorBagInterface $translatorBag): void; + + public function read(array $domains, array $locales): TranslatorBag; + + public function delete(TranslatorBagInterface $translatorBag): void; +} diff --git a/libraries/Symfony/Component/Translation/Provider/TranslationProviderCollection.php b/libraries/Symfony/Component/Translation/Provider/TranslationProviderCollection.php new file mode 100644 index 00000000000..5d97804752a --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/TranslationProviderCollection.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * @author Mathieu Santostefano + */ +final class TranslationProviderCollection +{ + /** + * @var array + */ + private array $providers; + + /** + * @param array $providers + */ + public function __construct(iterable $providers) + { + $this->providers = \is_array($providers) ? $providers : iterator_to_array($providers); + } + + public function __toString(): string + { + return '['.implode(',', array_keys($this->providers)).']'; + } + + public function has(string $name): bool + { + return isset($this->providers[$name]); + } + + public function get(string $name): ProviderInterface + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this)); + } + + return $this->providers[$name]; + } + + public function keys(): array + { + return array_keys($this->providers); + } +} diff --git a/libraries/Symfony/Component/Translation/Provider/TranslationProviderCollectionFactory.php b/libraries/Symfony/Component/Translation/Provider/TranslationProviderCollectionFactory.php new file mode 100644 index 00000000000..f3dea112577 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Provider/TranslationProviderCollectionFactory.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Provider; + +use EDD\Vendor\Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +/** + * @author Mathieu Santostefano + */ +class TranslationProviderCollectionFactory +{ + private iterable $factories; + private array $enabledLocales; + + /** + * @param iterable $factories + */ + public function __construct(iterable $factories, array $enabledLocales) + { + $this->factories = $factories; + $this->enabledLocales = $enabledLocales; + } + + public function fromConfig(array $config): TranslationProviderCollection + { + $providers = []; + foreach ($config as $name => $currentConfig) { + $providers[$name] = $this->fromDsnObject( + new Dsn($currentConfig['dsn']), + !$currentConfig['locales'] ? $this->enabledLocales : $currentConfig['locales'], + !$currentConfig['domains'] ? [] : $currentConfig['domains'] + ); + } + + return new TranslationProviderCollection($providers); + } + + public function fromDsnObject(Dsn $dsn, array $locales, array $domains = []): ProviderInterface + { + foreach ($this->factories as $factory) { + if ($factory->supports($dsn)) { + return new FilteringProvider($factory->create($dsn), $locales, $domains); + } + } + + throw new UnsupportedSchemeException($dsn); + } +} diff --git a/libraries/Symfony/Component/Translation/PseudoLocalizationTranslator.php b/libraries/Symfony/Component/Translation/PseudoLocalizationTranslator.php new file mode 100644 index 00000000000..2064a7929dc --- /dev/null +++ b/libraries/Symfony/Component/Translation/PseudoLocalizationTranslator.php @@ -0,0 +1,365 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; + +/** + * This translator should only be used in a development environment. + */ +final class PseudoLocalizationTranslator implements TranslatorInterface +{ + private const EXPANSION_CHARACTER = '~'; + + private TranslatorInterface $translator; + private bool $accents; + private float $expansionFactor; + private bool $brackets; + private bool $parseHTML; + + /** + * @var string[] + */ + private array $localizableHTMLAttributes; + + /** + * Available options: + * * accents: + * type: boolean + * default: true + * description: replace ASCII characters of the translated string with accented versions or similar characters + * example: if true, "foo" => "ƒöö". + * + * * expansion_factor: + * type: float + * default: 1 + * validation: it must be greater than or equal to 1 + * description: expand the translated string by the given factor with spaces and tildes + * example: if 2, "foo" => "~foo ~" + * + * * brackets: + * type: boolean + * default: true + * description: wrap the translated string with brackets + * example: if true, "foo" => "[foo]" + * + * * parse_html: + * type: boolean + * default: false + * description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML + * warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo
    bar" => "foo
    bar
    " + * + * * localizable_html_attributes: + * type: string[] + * default: [] + * description: the list of HTML attributes whose values can be altered - it is only useful when the "parse_html" option is set to true + * example: if ["title"], and with the "accents" option set to true, "Profile" => "Þŕöƒîļé" - if "title" was not in the "localizable_html_attributes" list, the title attribute data would be left unchanged. + */ + public function __construct(TranslatorInterface $translator, array $options = []) + { + $this->translator = $translator; + $this->accents = $options['accents'] ?? true; + + if (1.0 > ($this->expansionFactor = $options['expansion_factor'] ?? 1.0)) { + throw new \InvalidArgumentException('The expansion factor must be greater than or equal to 1.'); + } + + $this->brackets = $options['brackets'] ?? true; + + $this->parseHTML = $options['parse_html'] ?? false; + if ($this->parseHTML && !$this->accents && 1.0 === $this->expansionFactor) { + $this->parseHTML = false; + } + + $this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? []; + } + + public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + $trans = ''; + $visibleText = ''; + + foreach ($this->getParts($this->translator->trans($id, $parameters, $domain, $locale)) as [$visible, $localizable, $text]) { + if ($visible) { + $visibleText .= $text; + } + + if (!$localizable) { + $trans .= $text; + + continue; + } + + $this->addAccents($trans, $text); + } + + $this->expand($trans, $visibleText); + + $this->addBrackets($trans); + + return $trans; + } + + public function getLocale(): string + { + return $this->translator->getLocale(); + } + + private function getParts(string $originalTrans): array + { + if (!$this->parseHTML) { + return [[true, true, $originalTrans]]; + } + + $html = mb_encode_numericentity($originalTrans, [0x80, 0x10FFFF, 0, 0x1FFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8'); + + $useInternalErrors = libxml_use_internal_errors(true); + + $dom = new \DOMDocument(); + $dom->loadHTML(''.$html.''); + + libxml_clear_errors(); + libxml_use_internal_errors($useInternalErrors); + + return $this->parseNode($dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0)); + } + + private function parseNode(\DOMNode $node): array + { + $parts = []; + + foreach ($node->childNodes as $childNode) { + if (!$childNode instanceof \DOMElement) { + $parts[] = [true, true, $childNode->nodeValue]; + + continue; + } + + $parts[] = [false, false, '<'.$childNode->tagName]; + + /** @var \DOMAttr $attribute */ + foreach ($childNode->attributes as $attribute) { + $parts[] = [false, false, ' '.$attribute->nodeName.'="']; + + $localizableAttribute = \in_array($attribute->nodeName, $this->localizableHTMLAttributes, true); + foreach (preg_split('/(&(?:amp|quot|#039|lt|gt);+)/', htmlspecialchars($attribute->nodeValue, \ENT_QUOTES, 'UTF-8'), -1, \PREG_SPLIT_DELIM_CAPTURE) as $i => $match) { + if ('' === $match) { + continue; + } + + $parts[] = [false, $localizableAttribute && 0 === $i % 2, $match]; + } + + $parts[] = [false, false, '"']; + } + + $parts[] = [false, false, '>']; + + $parts = array_merge($parts, $this->parseNode($childNode, $parts)); + + $parts[] = [false, false, 'tagName.'>']; + } + + return $parts; + } + + private function addAccents(string &$trans, string $text): void + { + $trans .= $this->accents ? strtr($text, [ + ' ' => ' ', + '!' => '¡', + '"' => '″', + '#' => '♯', + '$' => '€', + '%' => '‰', + '&' => '⅋', + '\'' => '´', + '(' => '{', + ')' => '}', + '*' => '⁎', + '+' => '⁺', + ',' => '،', + '-' => '‐', + '.' => '·', + '/' => '⁄', + '0' => '⓪', + '1' => '①', + '2' => '②', + '3' => '③', + '4' => '④', + '5' => '⑤', + '6' => '⑥', + '7' => '⑦', + '8' => '⑧', + '9' => '⑨', + ':' => '∶', + ';' => '⁏', + '<' => '≤', + '=' => '≂', + '>' => '≥', + '?' => '¿', + '@' => '՞', + 'A' => 'Å', + 'B' => 'Ɓ', + 'C' => 'Ç', + 'D' => 'Ð', + 'E' => 'É', + 'F' => 'Ƒ', + 'G' => 'Ĝ', + 'H' => 'Ĥ', + 'I' => 'Î', + 'J' => 'Ĵ', + 'K' => 'Ķ', + 'L' => 'Ļ', + 'M' => 'Ṁ', + 'N' => 'Ñ', + 'O' => 'Ö', + 'P' => 'Þ', + 'Q' => 'Ǫ', + 'R' => 'Ŕ', + 'S' => 'Š', + 'T' => 'Ţ', + 'U' => 'Û', + 'V' => 'Ṽ', + 'W' => 'Ŵ', + 'X' => 'Ẋ', + 'Y' => 'Ý', + 'Z' => 'Ž', + '[' => '⁅', + '\\' => '∖', + ']' => '⁆', + '^' => '˄', + '_' => '‿', + '`' => '‵', + 'a' => 'å', + 'b' => 'ƀ', + 'c' => 'ç', + 'd' => 'ð', + 'e' => 'é', + 'f' => 'ƒ', + 'g' => 'ĝ', + 'h' => 'ĥ', + 'i' => 'î', + 'j' => 'ĵ', + 'k' => 'ķ', + 'l' => 'ļ', + 'm' => 'ɱ', + 'n' => 'ñ', + 'o' => 'ö', + 'p' => 'þ', + 'q' => 'ǫ', + 'r' => 'ŕ', + 's' => 'š', + 't' => 'ţ', + 'u' => 'û', + 'v' => 'ṽ', + 'w' => 'ŵ', + 'x' => 'ẋ', + 'y' => 'ý', + 'z' => 'ž', + '{' => '(', + '|' => '¦', + '}' => ')', + '~' => '˞', + ]) : $text; + } + + private function expand(string &$trans, string $visibleText): void + { + if (1.0 >= $this->expansionFactor) { + return; + } + + $visibleLength = $this->strlen($visibleText); + $missingLength = (int) ceil($visibleLength * $this->expansionFactor) - $visibleLength; + if ($this->brackets) { + $missingLength -= 2; + } + + if (0 >= $missingLength) { + return; + } + + $words = []; + $wordsCount = 0; + foreach (preg_split('/ +/', $visibleText, -1, \PREG_SPLIT_NO_EMPTY) as $word) { + $wordLength = $this->strlen($word); + + if ($wordLength >= $missingLength) { + continue; + } + + if (!isset($words[$wordLength])) { + $words[$wordLength] = 0; + } + + ++$words[$wordLength]; + ++$wordsCount; + } + + if (!$words) { + $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1); + + return; + } + + arsort($words, \SORT_NUMERIC); + + $longestWordLength = max(array_keys($words)); + + while (true) { + $r = mt_rand(1, $wordsCount); + + foreach ($words as $length => $count) { + $r -= $count; + if ($r <= 0) { + break; + } + } + + $trans .= ' '.str_repeat(self::EXPANSION_CHARACTER, $length); + + $missingLength -= $length + 1; + + if (0 === $missingLength) { + return; + } + + while ($longestWordLength >= $missingLength) { + $wordsCount -= $words[$longestWordLength]; + unset($words[$longestWordLength]); + + if (!$words) { + $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1); + + return; + } + + $longestWordLength = max(array_keys($words)); + } + } + } + + private function addBrackets(string &$trans): void + { + if (!$this->brackets) { + return; + } + + $trans = '['.$trans.']'; + } + + private function strlen(string $s): int + { + return false === ($encoding = mb_detect_encoding($s, null, true)) ? \strlen($s) : mb_strlen($s, $encoding); + } +} diff --git a/libraries/Symfony/Component/Translation/README.md b/libraries/Symfony/Component/Translation/README.md new file mode 100644 index 00000000000..32e4017b72e --- /dev/null +++ b/libraries/Symfony/Component/Translation/README.md @@ -0,0 +1,40 @@ +Translation Component +===================== + +The Translation component provides tools to internationalize your application. + +Getting Started +--------------- + +``` +$ composer require symfony/translation +``` + +```php +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\Loader\ArrayLoader; + +$translator = new Translator('fr_FR'); +$translator->addLoader('array', new ArrayLoader()); +$translator->addResource('array', [ + 'Hello World!' => 'Bonjour !', +], 'fr_FR'); + +echo $translator->trans('Hello World!'); // outputs « Bonjour ! » +``` + +Sponsor +------- + +Help Symfony by [sponsoring][1] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/translation.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/sponsor diff --git a/libraries/Symfony/Component/Translation/Reader/TranslationReader.php b/libraries/Symfony/Component/Translation/Reader/TranslationReader.php new file mode 100644 index 00000000000..3f8e34f9119 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Reader/TranslationReader.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Reader; + +use Symfony\Component\Finder\Finder; +use EDD\Vendor\Symfony\Component\Translation\Loader\LoaderInterface; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationReader reads translation messages from translation files. + * + * @author Michel Salib + */ +class TranslationReader implements TranslationReaderInterface +{ + /** + * Loaders used for import. + * + * @var array + */ + private array $loaders = []; + + /** + * Adds a loader to the translation extractor. + * + * @param string $format The format of the loader + * + * @return void + */ + public function addLoader(string $format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * @return void + */ + public function read(string $directory, MessageCatalogue $catalogue) + { + if (!is_dir($directory)) { + return; + } + + foreach ($this->loaders as $format => $loader) { + // load any existing translation files + $finder = new Finder(); + $extension = $catalogue->getLocale().'.'.$format; + $files = $finder->files()->name('*.'.$extension)->in($directory); + foreach ($files as $file) { + $domain = substr($file->getFilename(), 0, -1 * \strlen($extension) - 1); + $catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain)); + } + } + } +} diff --git a/libraries/Symfony/Component/Translation/Reader/TranslationReaderInterface.php b/libraries/Symfony/Component/Translation/Reader/TranslationReaderInterface.php new file mode 100644 index 00000000000..f38895cccea --- /dev/null +++ b/libraries/Symfony/Component/Translation/Reader/TranslationReaderInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Reader; + +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationReader reads translation messages from translation files. + * + * @author Tobias Nyholm + */ +interface TranslationReaderInterface +{ + /** + * Reads translation messages from a directory to the catalogue. + * + * @return void + */ + public function read(string $directory, MessageCatalogue $catalogue); +} diff --git a/libraries/Symfony/Component/Translation/Resources/bin/translation-status.php b/libraries/Symfony/Component/Translation/Resources/bin/translation-status.php new file mode 100644 index 00000000000..42fa1c69b33 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Resources/bin/translation-status.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== \PHP_SAPI) { + throw new Exception('This script must be run from the command line.'); +} + +$usageInstructions = << false, + // NULL = analyze all locales + 'locale_to_analyze' => null, + // append --incomplete to only show incomplete languages + 'include_completed_languages' => true, + // the reference files all the other translations are compared to + 'original_files' => [ + 'src/Symfony/Component/Form/Resources/translations/validators.en.xlf', + 'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf', + 'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf', + ], +]; + +$argc = $_SERVER['argc']; +$argv = $_SERVER['argv']; + +if ($argc > 4) { + echo str_replace('translation-status.php', $argv[0], $usageInstructions); + exit(1); +} + +foreach (array_slice($argv, 1) as $argumentOrOption) { + if ('--incomplete' === $argumentOrOption) { + $config['include_completed_languages'] = false; + continue; + } + + if (str_starts_with($argumentOrOption, '-')) { + $config['verbose_output'] = true; + } else { + $config['locale_to_analyze'] = $argumentOrOption; + } +} + +foreach ($config['original_files'] as $originalFilePath) { + if (!file_exists($originalFilePath)) { + echo sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', \PHP_EOL, $originalFilePath); + exit(1); + } +} + +$totalMissingTranslations = 0; +$totalTranslationMismatches = 0; + +foreach ($config['original_files'] as $originalFilePath) { + $translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']); + $translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths); + + $totalMissingTranslations += array_sum(array_map(fn ($translation) => count($translation['missingKeys']), array_values($translationStatus))); + $totalTranslationMismatches += array_sum(array_map(fn ($translation) => count($translation['mismatches']), array_values($translationStatus))); + + printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']); +} + +exit($totalTranslationMismatches > 0 ? 1 : 0); + +function findTranslationFiles($originalFilePath, $localeToAnalyze): array +{ + $translations = []; + + $translationsDir = dirname($originalFilePath); + $originalFileName = basename($originalFilePath); + $translationFileNamePattern = str_replace('.en.', '.*.', $originalFileName); + + $translationFiles = glob($translationsDir.'/'.$translationFileNamePattern, \GLOB_NOSORT); + sort($translationFiles); + foreach ($translationFiles as $filePath) { + $locale = extractLocaleFromFilePath($filePath); + + if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) { + continue; + } + + $translations[$locale] = $filePath; + } + + return $translations; +} + +function calculateTranslationStatus($originalFilePath, $translationFilePaths): array +{ + $translationStatus = []; + $allTranslationKeys = extractTranslationKeys($originalFilePath); + + foreach ($translationFilePaths as $locale => $translationPath) { + $translatedKeys = extractTranslationKeys($translationPath); + $missingKeys = array_diff_key($allTranslationKeys, $translatedKeys); + $mismatches = findTransUnitMismatches($allTranslationKeys, $translatedKeys); + + $translationStatus[$locale] = [ + 'total' => count($allTranslationKeys), + 'translated' => count($translatedKeys), + 'missingKeys' => $missingKeys, + 'mismatches' => $mismatches, + ]; + $translationStatus[$locale]['is_completed'] = isTranslationCompleted($translationStatus[$locale]); + } + + return $translationStatus; +} + +function isTranslationCompleted(array $translationStatus): bool +{ + return $translationStatus['total'] === $translationStatus['translated'] && 0 === count($translationStatus['mismatches']); +} + +function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput, $includeCompletedLanguages) +{ + printTitle($originalFilePath); + printTable($translationStatus, $verboseOutput, $includeCompletedLanguages); + echo \PHP_EOL.\PHP_EOL; +} + +function extractLocaleFromFilePath($filePath) +{ + $parts = explode('.', $filePath); + + return $parts[count($parts) - 2]; +} + +function extractTranslationKeys($filePath): array +{ + $translationKeys = []; + $contents = new SimpleXMLElement(file_get_contents($filePath)); + + foreach ($contents->file->body->{'trans-unit'} as $translationKey) { + $translationId = (string) $translationKey['id']; + $translationKey = (string) ($translationKey['resname'] ?? $translationKey->source); + + $translationKeys[$translationId] = $translationKey; + } + + return $translationKeys; +} + +/** + * Check whether the trans-unit id and source match with the base translation. + */ +function findTransUnitMismatches(array $baseTranslationKeys, array $translatedKeys): array +{ + $mismatches = []; + + foreach ($baseTranslationKeys as $translationId => $translationKey) { + if (!isset($translatedKeys[$translationId])) { + continue; + } + if ($translatedKeys[$translationId] !== $translationKey) { + $mismatches[$translationId] = [ + 'found' => $translatedKeys[$translationId], + 'expected' => $translationKey, + ]; + } + } + + return $mismatches; +} + +function printTitle($title) +{ + echo $title.\PHP_EOL; + echo str_repeat('=', strlen($title)).\PHP_EOL.\PHP_EOL; +} + +function printTable($translations, $verboseOutput, bool $includeCompletedLanguages) +{ + if (0 === count($translations)) { + echo 'No translations found'; + + return; + } + $longestLocaleNameLength = max(array_map('strlen', array_keys($translations))); + + foreach ($translations as $locale => $translation) { + if (!$includeCompletedLanguages && $translation['is_completed']) { + continue; + } + + if ($translation['translated'] > $translation['total']) { + textColorRed(); + } elseif (count($translation['mismatches']) > 0) { + textColorRed(); + } elseif ($translation['is_completed']) { + textColorGreen(); + } + + echo sprintf( + '| Locale: %-'.$longestLocaleNameLength.'s | Translated: %2d/%2d | Mismatches: %d |', + $locale, + $translation['translated'], + $translation['total'], + count($translation['mismatches']) + ).\PHP_EOL; + + textColorNormal(); + + $shouldBeClosed = false; + if (true === $verboseOutput && count($translation['missingKeys']) > 0) { + echo '| Missing Translations:'.\PHP_EOL; + + foreach ($translation['missingKeys'] as $id => $content) { + echo sprintf('| (id=%s) %s', $id, $content).\PHP_EOL; + } + $shouldBeClosed = true; + } + if (true === $verboseOutput && count($translation['mismatches']) > 0) { + echo '| Mismatches between trans-unit id and source:'.\PHP_EOL; + + foreach ($translation['mismatches'] as $id => $content) { + echo sprintf('| (id=%s) Expected: %s', $id, $content['expected']).\PHP_EOL; + echo sprintf('| Found: %s', $content['found']).\PHP_EOL; + } + $shouldBeClosed = true; + } + if ($shouldBeClosed) { + echo str_repeat('-', 80).\PHP_EOL; + } + } +} + +function textColorGreen() +{ + echo "\033[32m"; +} + +function textColorRed() +{ + echo "\033[31m"; +} + +function textColorNormal() +{ + echo "\033[0m"; +} diff --git a/libraries/Symfony/Component/Translation/Resources/data/parents.json b/libraries/Symfony/Component/Translation/Resources/data/parents.json new file mode 100644 index 00000000000..24d4d119e9d --- /dev/null +++ b/libraries/Symfony/Component/Translation/Resources/data/parents.json @@ -0,0 +1,142 @@ +{ + "az_Cyrl": "root", + "bs_Cyrl": "root", + "en_150": "en_001", + "en_AG": "en_001", + "en_AI": "en_001", + "en_AT": "en_150", + "en_AU": "en_001", + "en_BB": "en_001", + "en_BE": "en_150", + "en_BM": "en_001", + "en_BS": "en_001", + "en_BW": "en_001", + "en_BZ": "en_001", + "en_CC": "en_001", + "en_CH": "en_150", + "en_CK": "en_001", + "en_CM": "en_001", + "en_CX": "en_001", + "en_CY": "en_001", + "en_DE": "en_150", + "en_DG": "en_001", + "en_DK": "en_150", + "en_DM": "en_001", + "en_ER": "en_001", + "en_FI": "en_150", + "en_FJ": "en_001", + "en_FK": "en_001", + "en_FM": "en_001", + "en_GB": "en_001", + "en_GD": "en_001", + "en_GG": "en_001", + "en_GH": "en_001", + "en_GI": "en_001", + "en_GM": "en_001", + "en_GY": "en_001", + "en_HK": "en_001", + "en_ID": "en_001", + "en_IE": "en_001", + "en_IL": "en_001", + "en_IM": "en_001", + "en_IN": "en_001", + "en_IO": "en_001", + "en_JE": "en_001", + "en_JM": "en_001", + "en_KE": "en_001", + "en_KI": "en_001", + "en_KN": "en_001", + "en_KY": "en_001", + "en_LC": "en_001", + "en_LR": "en_001", + "en_LS": "en_001", + "en_MG": "en_001", + "en_MO": "en_001", + "en_MS": "en_001", + "en_MT": "en_001", + "en_MU": "en_001", + "en_MV": "en_001", + "en_MW": "en_001", + "en_MY": "en_001", + "en_NA": "en_001", + "en_NF": "en_001", + "en_NG": "en_001", + "en_NL": "en_150", + "en_NR": "en_001", + "en_NU": "en_001", + "en_NZ": "en_001", + "en_PG": "en_001", + "en_PK": "en_001", + "en_PN": "en_001", + "en_PW": "en_001", + "en_RW": "en_001", + "en_SB": "en_001", + "en_SC": "en_001", + "en_SD": "en_001", + "en_SE": "en_150", + "en_SG": "en_001", + "en_SH": "en_001", + "en_SI": "en_150", + "en_SL": "en_001", + "en_SS": "en_001", + "en_SX": "en_001", + "en_SZ": "en_001", + "en_TC": "en_001", + "en_TK": "en_001", + "en_TO": "en_001", + "en_TT": "en_001", + "en_TV": "en_001", + "en_TZ": "en_001", + "en_UG": "en_001", + "en_VC": "en_001", + "en_VG": "en_001", + "en_VU": "en_001", + "en_WS": "en_001", + "en_ZA": "en_001", + "en_ZM": "en_001", + "en_ZW": "en_001", + "es_AR": "es_419", + "es_BO": "es_419", + "es_BR": "es_419", + "es_BZ": "es_419", + "es_CL": "es_419", + "es_CO": "es_419", + "es_CR": "es_419", + "es_CU": "es_419", + "es_DO": "es_419", + "es_EC": "es_419", + "es_GT": "es_419", + "es_HN": "es_419", + "es_MX": "es_419", + "es_NI": "es_419", + "es_PA": "es_419", + "es_PE": "es_419", + "es_PR": "es_419", + "es_PY": "es_419", + "es_SV": "es_419", + "es_US": "es_419", + "es_UY": "es_419", + "es_VE": "es_419", + "ff_Adlm": "root", + "hi_Latn": "en_IN", + "ks_Deva": "root", + "nb": "no", + "nn": "no", + "pa_Arab": "root", + "pt_AO": "pt_PT", + "pt_CH": "pt_PT", + "pt_CV": "pt_PT", + "pt_GQ": "pt_PT", + "pt_GW": "pt_PT", + "pt_LU": "pt_PT", + "pt_MO": "pt_PT", + "pt_MZ": "pt_PT", + "pt_ST": "pt_PT", + "pt_TL": "pt_PT", + "sd_Deva": "root", + "sr_Latn": "root", + "uz_Arab": "root", + "uz_Cyrl": "root", + "zh_Hant": "root", + "zh_Hant_MO": "zh_Hant_HK" +} diff --git a/libraries/Symfony/Component/Translation/Resources/functions.php b/libraries/Symfony/Component/Translation/Resources/functions.php new file mode 100644 index 00000000000..50e2e92df8a --- /dev/null +++ b/libraries/Symfony/Component/Translation/Resources/functions.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +if (!\function_exists(t::class)) { + /** + * @author Nate Wiebe + */ + function t(string $message, array $parameters = [], ?string $domain = null): TranslatableMessage + { + return new TranslatableMessage($message, $parameters, $domain); + } +} diff --git a/libraries/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-transitional.xsd b/libraries/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-transitional.xsd new file mode 100644 index 00000000000..1f38de72f4c --- /dev/null +++ b/libraries/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-transitional.xsd @@ -0,0 +1,2261 @@ + + + + + + + + + + + + + + Values for the attribute 'context-type'. + + + + + Indicates a database content. + + + + + Indicates the content of an element within an XML document. + + + + + Indicates the name of an element within an XML document. + + + + + Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found. + + + + + Indicates a the number of parameters contained within the <source>. + + + + + Indicates notes pertaining to the parameters in the <source>. + + + + + Indicates the content of a record within a database. + + + + + Indicates the name of a record within a database. + + + + + Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file. + + + + + + + Values for the attribute 'count-type'. + + + + + Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts. + + + + + Indicates the count units are translation units existing already in the same document. + + + + + Indicates a total count. + + + + + + + Values for the attribute 'ctype' when used other elements than <ph> or <x>. + + + + + Indicates a run of bolded text. + + + + + Indicates a run of text in italics. + + + + + Indicates a run of underlined text. + + + + + Indicates a run of hyper-text. + + + + + + + Values for the attribute 'ctype' when used with <ph> or <x>. + + + + + Indicates a inline image. + + + + + Indicates a page break. + + + + + Indicates a line break. + + + + + + + + + + + + Values for the attribute 'datatype'. + + + + + Indicates Active Server Page data. + + + + + Indicates C source file data. + + + + + Indicates Channel Definition Format (CDF) data. + + + + + Indicates ColdFusion data. + + + + + Indicates C++ source file data. + + + + + Indicates C-Sharp data. + + + + + Indicates strings from C, ASM, and driver files data. + + + + + Indicates comma-separated values data. + + + + + Indicates database data. + + + + + Indicates portions of document that follows data and contains metadata. + + + + + Indicates portions of document that precedes data and contains metadata. + + + + + Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import). + + + + + Indicates standard user input screen data. + + + + + Indicates HyperText Markup Language (HTML) data - document instance. + + + + + Indicates content within an HTML document’s <body> element. + + + + + Indicates Windows INI file data. + + + + + Indicates Interleaf data. + + + + + Indicates Java source file data (extension '.java'). + + + + + Indicates Java property resource bundle data. + + + + + Indicates Java list resource bundle data. + + + + + Indicates JavaScript source file data. + + + + + Indicates JScript source file data. + + + + + Indicates information relating to formatting. + + + + + Indicates LISP source file data. + + + + + Indicates information relating to margin formats. + + + + + Indicates a file containing menu. + + + + + Indicates numerically identified string table. + + + + + Indicates Maker Interchange Format (MIF) data. + + + + + Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute. + + + + + Indicates GNU Machine Object data. + + + + + Indicates Message Librarian strings created by Novell's Message Librarian Tool. + + + + + Indicates information to be displayed at the bottom of each page of a document. + + + + + Indicates information to be displayed at the top of each page of a document. + + + + + Indicates a list of property values (e.g., settings within INI files or preferences dialog). + + + + + Indicates Pascal source file data. + + + + + Indicates Hypertext Preprocessor data. + + + + + Indicates plain text file (no formatting other than, possibly, wrapping). + + + + + Indicates GNU Portable Object file. + + + + + Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc. + + + + + Indicates Windows .NET binary resources. + + + + + Indicates Windows .NET Resources. + + + + + Indicates Rich Text Format (RTF) data. + + + + + Indicates Standard Generalized Markup Language (SGML) data - document instance. + + + + + Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD). + + + + + Indicates Scalable Vector Graphic (SVG) data. + + + + + Indicates VisualBasic Script source file. + + + + + Indicates warning message. + + + + + Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file). + + + + + Indicates Extensible HyperText Markup Language (XHTML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD). + + + + + Indicates Extensible Stylesheet Language (XSL) data. + + + + + Indicates XUL elements. + + + + + + + Values for the attribute 'mtype'. + + + + + Indicates the marked text is an abbreviation. + + + + + ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept. + + + + + ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective'). + + + + + ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging'). + + + + + ISO-12620: A proper-name term, such as the name of an agency or other proper entity. + + + + + ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another. + + + + + ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language. + + + + + Indicates the marked text is a date and/or time. + + + + + ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign. + + + + + ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form. + + + + + ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula. + + + + + ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record. + + + + + ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy'). + + + + + ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body. + + + + + ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages. + + + + + ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like. + + + + + ISO-12620 2.1.17: A unit to track object. + + + + + Indicates the marked text is a name. + + + + + ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others. + + + + + ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system. + + + + + Indicates the marked text is a phrase. + + + + + ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase. + + + + + Indicates the marked text should not be translated. + + + + + ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet. + + + + + Indicates that the marked text represents a segment. + + + + + ISO-12620 2.1.18.2: A fixed, lexicalized phrase. + + + + + ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs'). + + + + + ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system. + + + + + ISO-12620 2.1.19: A fixed chunk of recurring text. + + + + + ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof. + + + + + ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry. + + + + + ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language. + + + + + Indicates the marked text is a term. + + + + + ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted. + + + + + ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system. + + + + + ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza'). + + + + + ISO-12620 2.1.9: One of the alternate forms of a term. + + + + + + + Values for the attribute 'restype'. + + + + + Indicates a Windows RC AUTO3STATE control. + + + + + Indicates a Windows RC AUTOCHECKBOX control. + + + + + Indicates a Windows RC AUTORADIOBUTTON control. + + + + + Indicates a Windows RC BEDIT control. + + + + + Indicates a bitmap, for example a BITMAP resource in Windows. + + + + + Indicates a button object, for example a BUTTON control Windows. + + + + + Indicates a caption, such as the caption of a dialog box. + + + + + Indicates the cell in a table, for example the content of the <td> element in HTML. + + + + + Indicates check box object, for example a CHECKBOX control in Windows. + + + + + Indicates a menu item with an associated checkbox. + + + + + Indicates a list box, but with a check-box for each item. + + + + + Indicates a color selection dialog. + + + + + Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows. + + + + + Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234). + + + + + Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403). + + + + + Indicates a UI base class element that cannot be represented by any other element. + + + + + Indicates a context menu. + + + + + Indicates a Windows RC CTEXT control. + + + + + Indicates a cursor, for example a CURSOR resource in Windows. + + + + + Indicates a date/time picker. + + + + + Indicates a Windows RC DEFPUSHBUTTON control. + + + + + Indicates a dialog box. + + + + + Indicates a Windows RC DLGINIT resource block. + + + + + Indicates an edit box object, for example an EDIT control in Windows. + + + + + Indicates a filename. + + + + + Indicates a file dialog. + + + + + Indicates a footnote. + + + + + Indicates a font name. + + + + + Indicates a footer. + + + + + Indicates a frame object. + + + + + Indicates a XUL grid element. + + + + + Indicates a groupbox object, for example a GROUPBOX control in Windows. + + + + + Indicates a header item. + + + + + Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML. + + + + + Indicates a Windows RC HEDIT control. + + + + + Indicates a horizontal scrollbar. + + + + + Indicates an icon, for example an ICON resource in Windows. + + + + + Indicates a Windows RC IEDIT control. + + + + + Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF. + + + + + Indicates a label object. + + + + + Indicates a label that is also a HTML link (not necessarily a URL). + + + + + Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML). + + + + + Indicates a listbox object, for example an LISTBOX control in Windows. + + + + + Indicates an list item (an entry in a list). + + + + + Indicates a Windows RC LTEXT control. + + + + + Indicates a menu (a group of menu-items). + + + + + Indicates a toolbar containing one or more tope level menus. + + + + + Indicates a menu item (an entry in a menu). + + + + + Indicates a XUL menuseparator element. + + + + + Indicates a message, for example an entry in a MESSAGETABLE resource in Windows. + + + + + Indicates a calendar control. + + + + + Indicates an edit box beside a spin control. + + + + + Indicates a catch all for rectangular areas. + + + + + Indicates a standalone menu not necessarily associated with a menubar. + + + + + Indicates a pushbox object, for example a PUSHBOX control in Windows. + + + + + Indicates a Windows RC PUSHBUTTON control. + + + + + Indicates a radio button object. + + + + + Indicates a menuitem with associated radio button. + + + + + Indicates raw data resources for an application. + + + + + Indicates a row in a table. + + + + + Indicates a Windows RC RTEXT control. + + + + + Indicates a user navigable container used to show a portion of a document. + + + + + Indicates a generic divider object (e.g. menu group separator). + + + + + Windows accelerators, shortcuts in resource or property files. + + + + + Indicates a UI control to indicate process activity but not progress. + + + + + Indicates a splitter bar. + + + + + Indicates a Windows RC STATE3 control. + + + + + Indicates a window for providing feedback to the users, like 'read-only', etc. + + + + + Indicates a string, for example an entry in a STRINGTABLE resource in Windows. + + + + + Indicates a layers of controls with a tab to select layers. + + + + + Indicates a display and edits regular two-dimensional tables of cells. + + + + + Indicates a XUL textbox element. + + + + + Indicates a UI button that can be toggled to on or off state. + + + + + Indicates an array of controls, usually buttons. + + + + + Indicates a pop up tool tip text. + + + + + Indicates a bar with a pointer indicating a position within a certain range. + + + + + Indicates a control that displays a set of hierarchical data. + + + + + Indicates a URI (URN or URL). + + + + + Indicates a Windows RC USERBUTTON control. + + + + + Indicates a user-defined control like CONTROL control in Windows. + + + + + Indicates the text of a variable. + + + + + Indicates version information about a resource like VERSIONINFO in Windows. + + + + + Indicates a vertical scrollbar. + + + + + Indicates a graphical window. + + + + + + + Values for the attribute 'size-unit'. + + + + + Indicates a size in 8-bit bytes. + + + + + Indicates a size in Unicode characters. + + + + + Indicates a size in columns. Used for HTML text area. + + + + + Indicates a size in centimeters. + + + + + Indicates a size in dialog units, as defined in Windows resources. + + + + + Indicates a size in 'font-size' units (as defined in CSS). + + + + + Indicates a size in 'x-height' units (as defined in CSS). + + + + + Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster' + + + + + Indicates a size in inches. + + + + + Indicates a size in millimeters. + + + + + Indicates a size in percentage. + + + + + Indicates a size in pixels. + + + + + Indicates a size in point. + + + + + Indicates a size in rows. Used for HTML text area. + + + + + + + Values for the attribute 'state'. + + + + + Indicates the terminating state. + + + + + Indicates only non-textual information needs adaptation. + + + + + Indicates both text and non-textual information needs adaptation. + + + + + Indicates only non-textual information needs review. + + + + + Indicates both text and non-textual information needs review. + + + + + Indicates that only the text of the item needs to be reviewed. + + + + + Indicates that the item needs to be translated. + + + + + Indicates that the item is new. For example, translation units that were not in a previous version of the document. + + + + + Indicates that changes are reviewed and approved. + + + + + Indicates that the item has been translated. + + + + + + + Values for the attribute 'state-qualifier'. + + + + + Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously. + + + + + Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.). + + + + + Indicates a match based on matching IDs (in addition to matching text). + + + + + Indicates a translation derived from a glossary. + + + + + Indicates a translation derived from existing translation. + + + + + Indicates a translation derived from machine translation. + + + + + Indicates a translation derived from a translation repository. + + + + + Indicates a translation derived from a translation memory. + + + + + Indicates the translation is suggested by machine translation. + + + + + Indicates that the item has been rejected because of incorrect grammar. + + + + + Indicates that the item has been rejected because it is incorrect. + + + + + Indicates that the item has been rejected because it is too long or too short. + + + + + Indicates that the item has been rejected because of incorrect spelling. + + + + + Indicates the translation is suggested by translation memory. + + + + + + + Values for the attribute 'unit'. + + + + + Refers to words. + + + + + Refers to pages. + + + + + Refers to <trans-unit> elements. + + + + + Refers to <bin-unit> elements. + + + + + Refers to glyphs. + + + + + Refers to <trans-unit> and/or <bin-unit> elements. + + + + + Refers to the occurrences of instances defined by the count-type value. + + + + + Refers to characters. + + + + + Refers to lines. + + + + + Refers to sentences. + + + + + Refers to paragraphs. + + + + + Refers to segments. + + + + + Refers to placeables (inline elements). + + + + + + + Values for the attribute 'priority'. + + + + + Highest priority. + + + + + High priority. + + + + + High priority, but not as important as 2. + + + + + High priority, but not as important as 3. + + + + + Medium priority, but more important than 6. + + + + + Medium priority, but less important than 5. + + + + + Low priority, but more important than 8. + + + + + Low priority, but more important than 9. + + + + + Low priority. + + + + + Lowest priority. + + + + + + + + + This value indicates that all properties can be reformatted. This value must be used alone. + + + + + This value indicates that no properties should be reformatted. This value must be used alone. + + + + + + + + + + + + + This value indicates that all information in the coord attribute can be modified. + + + + + This value indicates that the x information in the coord attribute can be modified. + + + + + This value indicates that the y information in the coord attribute can be modified. + + + + + This value indicates that the cx information in the coord attribute can be modified. + + + + + This value indicates that the cy information in the coord attribute can be modified. + + + + + This value indicates that all the information in the font attribute can be modified. + + + + + This value indicates that the name information in the font attribute can be modified. + + + + + This value indicates that the size information in the font attribute can be modified. + + + + + This value indicates that the weight information in the font attribute can be modified. + + + + + This value indicates that the information in the css-style attribute can be modified. + + + + + This value indicates that the information in the style attribute can be modified. + + + + + This value indicates that the information in the exstyle attribute can be modified. + + + + + + + + + + + + + Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document. + + + + + Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed. + + + + + Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed. + + + + + + + + + Represents a translation proposal from a translation memory or other resource. + + + + + Represents a previous version of the target element. + + + + + Represents a rejected version of the target element. + + + + + Represents a translation to be used for reference purposes only, for example from a related product or a different language. + + + + + Represents a proposed translation that was used for the translation of the trans-unit, possibly modified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Values for the attribute 'coord'. + + + + + + + + Version values: 1.0 and 1.1 are allowed for backward compatibility. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/Symfony/Component/Translation/Resources/schemas/xliff-core-2.0.xsd b/libraries/Symfony/Component/Translation/Resources/schemas/xliff-core-2.0.xsd new file mode 100644 index 00000000000..963232f9721 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Resources/schemas/xliff-core-2.0.xsd @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/Symfony/Component/Translation/Resources/schemas/xml.xsd b/libraries/Symfony/Component/Translation/Resources/schemas/xml.xsd new file mode 100644 index 00000000000..a46162a7a7a --- /dev/null +++ b/libraries/Symfony/Component/Translation/Resources/schemas/xml.xsd @@ -0,0 +1,309 @@ + + + + + + +
    +

    About the XML namespace

    + +
    +

    + + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

    +

    + See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

    + +

    + Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

    +

    + See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

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

    lang (as an attribute name)

    +

    + + denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

    + +
    +
    +

    Notes

    +

    + Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

    +

    + + See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

    +

    + + The union allows for the 'un-declaration' of xml:lang with + the empty string. +

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

    space (as an attribute name)

    +

    + denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

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

    base (as an attribute name)

    +

    + denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

    + +

    + See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

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

    id (as an attribute name)

    +

    + + denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

    + +

    + See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

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

    Father (in any context at all)

    + +
    +

    + denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

    +
    +

    + + In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

    +
    +
    +
    +
    +
    + + + + +
    +

    About this schema document

    + +
    +

    + This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

    + +

    + To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

    +
    +          <schema.. .>
    +          .. .
    +           <import namespace="http://www.w3.org/XML/1998/namespace"
    +                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
    +     
    +

    + or +

    +
    +
    +           <import namespace="http://www.w3.org/XML/1998/namespace"
    +                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
    +     
    +

    + Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

    +
    +          <type.. .>
    +          .. .
    +           <attributeGroup ref="xml:specialAttrs"/>
    +     
    +

    + will define a type which will schema-validate an instance element + with any of those attributes. +

    + +
    +
    +
    +
    + + + +
    +

    Versioning policy for this schema document

    + +
    +

    + In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

    +

    + At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

    + +

    + The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

    +

    + + Previous dated (and unchanging) versions of this schema + document are at: +

    + +
    +
    +
    +
    + +
    diff --git a/libraries/Symfony/Component/Translation/Test/ProviderFactoryTestCase.php b/libraries/Symfony/Component/Translation/Test/ProviderFactoryTestCase.php new file mode 100644 index 00000000000..4940be81b02 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Test/ProviderFactoryTestCase.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Test; + +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\MockHttpClient; +use EDD\Vendor\Symfony\Component\Translation\Dumper\XliffFileDumper; +use EDD\Vendor\Symfony\Component\Translation\Exception\IncompleteDsnException; +use EDD\Vendor\Symfony\Component\Translation\Exception\UnsupportedSchemeException; +use EDD\Vendor\Symfony\Component\Translation\Loader\LoaderInterface; +use EDD\Vendor\Symfony\Component\Translation\Provider\Dsn; +use EDD\Vendor\Symfony\Component\Translation\Provider\ProviderFactoryInterface; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBagInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A test case to ease testing a translation provider factory. + * + * @author Mathieu Santostefano + */ +abstract class ProviderFactoryTestCase extends TestCase +{ + protected HttpClientInterface $client; + protected LoggerInterface|MockObject $logger; + protected string $defaultLocale; + protected LoaderInterface|MockObject $loader; + protected XliffFileDumper|MockObject $xliffFileDumper; + protected TranslatorBagInterface|MockObject $translatorBag; + + abstract public function createFactory(): ProviderFactoryInterface; + + /** + * @return iterable + */ + abstract public static function supportsProvider(): iterable; + + /** + * @return iterable + */ + abstract public static function createProvider(): iterable; + + /** + * @return iterable + */ + public static function unsupportedSchemeProvider(): iterable + { + return []; + } + + /** + * @return iterable + */ + public static function incompleteDsnProvider(): iterable + { + return []; + } + + /** + * @dataProvider supportsProvider + */ + public function testSupports(bool $expected, string $dsn) + { + $factory = $this->createFactory(); + + $this->assertSame($expected, $factory->supports(new Dsn($dsn))); + } + + /** + * @dataProvider createProvider + */ + public function testCreate(string $expected, string $dsn) + { + $factory = $this->createFactory(); + $provider = $factory->create(new Dsn($dsn)); + + $this->assertSame($expected, (string) $provider); + } + + /** + * @dataProvider unsupportedSchemeProvider + */ + public function testUnsupportedSchemeException(string $dsn, ?string $message = null) + { + $factory = $this->createFactory(); + + $dsn = new Dsn($dsn); + + $this->expectException(UnsupportedSchemeException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } + + /** + * @dataProvider incompleteDsnProvider + */ + public function testIncompleteDsnException(string $dsn, ?string $message = null) + { + $factory = $this->createFactory(); + + $dsn = new Dsn($dsn); + + $this->expectException(IncompleteDsnException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } + + protected function getClient(): HttpClientInterface + { + return $this->client ??= new MockHttpClient(); + } + + protected function getLogger(): LoggerInterface + { + return $this->logger ??= $this->createMock(LoggerInterface::class); + } + + protected function getDefaultLocale(): string + { + return $this->defaultLocale ??= 'en'; + } + + protected function getLoader(): LoaderInterface + { + return $this->loader ??= $this->createMock(LoaderInterface::class); + } + + protected function getXliffFileDumper(): XliffFileDumper + { + return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class); + } + + protected function getTranslatorBag(): TranslatorBagInterface + { + return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class); + } +} diff --git a/libraries/Symfony/Component/Translation/Test/ProviderTestCase.php b/libraries/Symfony/Component/Translation/Test/ProviderTestCase.php new file mode 100644 index 00000000000..ecb9b483331 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Test/ProviderTestCase.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Test; + +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\MockHttpClient; +use EDD\Vendor\Symfony\Component\Translation\Dumper\XliffFileDumper; +use EDD\Vendor\Symfony\Component\Translation\Loader\LoaderInterface; +use EDD\Vendor\Symfony\Component\Translation\Provider\ProviderInterface; +use EDD\Vendor\Symfony\Component\Translation\TranslatorBagInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A test case to ease testing a translation provider. + * + * @author Mathieu Santostefano + */ +abstract class ProviderTestCase extends TestCase +{ + protected HttpClientInterface $client; + protected LoggerInterface|MockObject $logger; + protected string $defaultLocale; + protected LoaderInterface|MockObject $loader; + protected XliffFileDumper|MockObject $xliffFileDumper; + protected TranslatorBagInterface|MockObject $translatorBag; + + abstract public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface; + + /** + * @return iterable + */ + abstract public static function toStringProvider(): iterable; + + /** + * @dataProvider toStringProvider + */ + public function testToString(ProviderInterface $provider, string $expected) + { + $this->assertSame($expected, (string) $provider); + } + + protected function getClient(): MockHttpClient + { + return $this->client ??= new MockHttpClient(); + } + + protected function getLoader(): LoaderInterface + { + return $this->loader ??= $this->createMock(LoaderInterface::class); + } + + protected function getLogger(): LoggerInterface + { + return $this->logger ??= $this->createMock(LoggerInterface::class); + } + + protected function getDefaultLocale(): string + { + return $this->defaultLocale ??= 'en'; + } + + protected function getXliffFileDumper(): XliffFileDumper + { + return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class); + } + + protected function getTranslatorBag(): TranslatorBagInterface + { + return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class); + } +} diff --git a/libraries/Symfony/Component/Translation/TranslatableMessage.php b/libraries/Symfony/Component/Translation/TranslatableMessage.php new file mode 100644 index 00000000000..85ce2be6ca1 --- /dev/null +++ b/libraries/Symfony/Component/Translation/TranslatableMessage.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use EDD\Vendor\Symfony\Contracts\Translation\TranslatableInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Nate Wiebe + */ +class TranslatableMessage implements TranslatableInterface +{ + private string $message; + private array $parameters; + private ?string $domain; + + public function __construct(string $message, array $parameters = [], ?string $domain = null) + { + $this->message = $message; + $this->parameters = $parameters; + $this->domain = $domain; + } + + public function __toString(): string + { + return $this->getMessage(); + } + + public function getMessage(): string + { + return $this->message; + } + + public function getParameters(): array + { + return $this->parameters; + } + + public function getDomain(): ?string + { + return $this->domain; + } + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + return $translator->trans($this->getMessage(), array_map( + static fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter, + $this->getParameters() + ), $this->getDomain(), $locale); + } +} diff --git a/libraries/Symfony/Component/Translation/Translator.php b/libraries/Symfony/Component/Translation/Translator.php new file mode 100644 index 00000000000..99ba5e5e3c4 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Translator.php @@ -0,0 +1,472 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use Symfony\Component\Config\ConfigCacheFactory; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheInterface; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\Exception\NotFoundResourceException; +use EDD\Vendor\Symfony\Component\Translation\Exception\RuntimeException; +use EDD\Vendor\Symfony\Component\Translation\Formatter\IntlFormatterInterface; +use EDD\Vendor\Symfony\Component\Translation\Formatter\MessageFormatter; +use EDD\Vendor\Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use EDD\Vendor\Symfony\Component\Translation\Loader\LoaderInterface; +use EDD\Vendor\Symfony\Contracts\Translation\LocaleAwareInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatableInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(MessageCatalogue::class); + +/** + * @author Fabien Potencier + */ +class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface +{ + /** + * @var MessageCatalogueInterface[] + */ + protected $catalogues = []; + + private string $locale; + + /** + * @var string[] + */ + private array $fallbackLocales = []; + + /** + * @var LoaderInterface[] + */ + private array $loaders = []; + + private array $resources = []; + + private MessageFormatterInterface $formatter; + + private ?string $cacheDir; + + private bool $debug; + + private array $cacheVary; + + private ?ConfigCacheFactoryInterface $configCacheFactory; + + private array $parentLocales; + + private bool $hasIntlFormatter; + + /** + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function __construct(string $locale, ?MessageFormatterInterface $formatter = null, ?string $cacheDir = null, bool $debug = false, array $cacheVary = []) + { + $this->setLocale($locale); + + $this->formatter = $formatter ??= new MessageFormatter(); + $this->cacheDir = $cacheDir; + $this->debug = $debug; + $this->cacheVary = $cacheVary; + $this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface; + } + + /** + * @return void + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + + /** + * Adds a Loader. + * + * @param string $format The name of the loader (@see addResource()) + * + * @return void + */ + public function addLoader(string $format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * Adds a Resource. + * + * @param string $format The name of the loader (@see addLoader()) + * @param mixed $resource The resource name + * + * @return void + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null) + { + $domain ??= 'messages'; + + $this->assertValidLocale($locale); + $locale ?: $locale = class_exists(\Locale::class) ? \Locale::getDefault() : 'en'; + + $this->resources[$locale][] = [$format, $resource, $domain]; + + if (\in_array($locale, $this->fallbackLocales)) { + $this->catalogues = []; + } else { + unset($this->catalogues[$locale]); + } + } + + /** + * @return void + */ + public function setLocale(string $locale) + { + $this->assertValidLocale($locale); + $this->locale = $locale; + } + + public function getLocale(): string + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + /** + * Sets the fallback locales. + * + * @param string[] $locales + * + * @return void + * + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function setFallbackLocales(array $locales) + { + // needed as the fallback locales are linked to the already loaded catalogues + $this->catalogues = []; + + foreach ($locales as $locale) { + $this->assertValidLocale($locale); + } + + $this->fallbackLocales = $this->cacheVary['fallback_locales'] = $locales; + } + + /** + * Gets the fallback locales. + * + * @internal + */ + public function getFallbackLocales(): array + { + return $this->fallbackLocales; + } + + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + if (null === $id || '' === $id) { + return ''; + } + + $domain ??= 'messages'; + + $catalogue = $this->getCatalogue($locale); + $locale = $catalogue->getLocale(); + while (!$catalogue->defines($id, $domain)) { + if ($cat = $catalogue->getFallbackCatalogue()) { + $catalogue = $cat; + $locale = $catalogue->getLocale(); + } else { + break; + } + } + + $parameters = array_map(fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($this, $locale) : $parameter, $parameters); + + $len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX); + if ($this->hasIntlFormatter + && ($catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX) + || (\strlen($domain) > $len && 0 === substr_compare($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX, -$len, $len))) + ) { + return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters); + } + + return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters); + } + + public function getCatalogue(?string $locale = null): MessageCatalogueInterface + { + if (!$locale) { + $locale = $this->getLocale(); + } else { + $this->assertValidLocale($locale); + } + + if (!isset($this->catalogues[$locale])) { + $this->loadCatalogue($locale); + } + + return $this->catalogues[$locale]; + } + + public function getCatalogues(): array + { + return array_values($this->catalogues); + } + + /** + * Gets the loaders. + * + * @return LoaderInterface[] + */ + protected function getLoaders(): array + { + return $this->loaders; + } + + /** + * @return void + */ + protected function loadCatalogue(string $locale) + { + if (null === $this->cacheDir) { + $this->initializeCatalogue($locale); + } else { + $this->initializeCacheCatalogue($locale); + } + } + + /** + * @return void + */ + protected function initializeCatalogue(string $locale) + { + $this->assertValidLocale($locale); + + try { + $this->doLoadCatalogue($locale); + } catch (NotFoundResourceException $e) { + if (!$this->computeFallbackLocales($locale)) { + throw $e; + } + } + $this->loadFallbackCatalogues($locale); + } + + private function initializeCacheCatalogue(string $locale): void + { + if (isset($this->catalogues[$locale])) { + /* Catalogue already initialized. */ + return; + } + + $this->assertValidLocale($locale); + $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale), + function (ConfigCacheInterface $cache) use ($locale) { + $this->dumpCatalogue($locale, $cache); + } + ); + + if (isset($this->catalogues[$locale])) { + /* Catalogue has been initialized as it was written out to cache. */ + return; + } + + /* Read catalogue from cache. */ + $this->catalogues[$locale] = include $cache->getPath(); + } + + private function dumpCatalogue(string $locale, ConfigCacheInterface $cache): void + { + $this->initializeCatalogue($locale); + $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]); + + $content = sprintf(<<getAllMessages($this->catalogues[$locale]), true), + $fallbackContent + ); + + $cache->write($content, $this->catalogues[$locale]->getResources()); + } + + private function getFallbackContent(MessageCatalogue $catalogue): string + { + $fallbackContent = ''; + $current = ''; + $replacementPattern = '/[^a-z0-9_]/i'; + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + $fallback = $fallbackCatalogue->getLocale(); + $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); + $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); + + $fallbackContent .= sprintf(<<<'EOF' +$catalogue%s = new MessageCatalogue('%s', %s); +$catalogue%s->addFallbackCatalogue($catalogue%s); + +EOF + , + $fallbackSuffix, + $fallback, + var_export($this->getAllMessages($fallbackCatalogue), true), + $currentSuffix, + $fallbackSuffix + ); + $current = $fallbackCatalogue->getLocale(); + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + + return $fallbackContent; + } + + private function getCatalogueCachePath(string $locale): string + { + return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->cacheVary), true)), 0, 7), '/', '_').'.php'; + } + + /** + * @internal + */ + protected function doLoadCatalogue(string $locale): void + { + $this->catalogues[$locale] = new MessageCatalogue($locale); + + if (isset($this->resources[$locale])) { + foreach ($this->resources[$locale] as $resource) { + if (!isset($this->loaders[$resource[0]])) { + if (\is_string($resource[1])) { + throw new RuntimeException(sprintf('No loader is registered for the "%s" format when loading the "%s" resource.', $resource[0], $resource[1])); + } + + throw new RuntimeException(sprintf('No loader is registered for the "%s" format.', $resource[0])); + } + $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); + } + } + } + + private function loadFallbackCatalogues(string $locale): void + { + $current = $this->catalogues[$locale]; + + foreach ($this->computeFallbackLocales($locale) as $fallback) { + if (!isset($this->catalogues[$fallback])) { + $this->initializeCatalogue($fallback); + } + + $fallbackCatalogue = new MessageCatalogue($fallback, $this->getAllMessages($this->catalogues[$fallback])); + foreach ($this->catalogues[$fallback]->getResources() as $resource) { + $fallbackCatalogue->addResource($resource); + } + $current->addFallbackCatalogue($fallbackCatalogue); + $current = $fallbackCatalogue; + } + } + + /** + * @return array + */ + protected function computeFallbackLocales(string $locale) + { + $this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); + + $originLocale = $locale; + $locales = []; + + while ($locale) { + $parent = $this->parentLocales[$locale] ?? null; + + if ($parent) { + $locale = 'root' !== $parent ? $parent : null; + } elseif (\function_exists('locale_parse')) { + $localeSubTags = locale_parse($locale); + $locale = null; + if (1 < \count($localeSubTags)) { + array_pop($localeSubTags); + $locale = locale_compose($localeSubTags) ?: null; + } + } elseif ($i = strrpos($locale, '_') ?: strrpos($locale, '-')) { + $locale = substr($locale, 0, $i); + } else { + $locale = null; + } + + if (null !== $locale) { + $locales[] = $locale; + } + } + + foreach ($this->fallbackLocales as $fallback) { + if ($fallback === $originLocale) { + continue; + } + + $locales[] = $fallback; + } + + return array_unique($locales); + } + + /** + * Asserts that the locale is valid, throws an Exception if not. + * + * @return void + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + protected function assertValidLocale(string $locale) + { + if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { + throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); + } + } + + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + */ + private function getConfigCacheFactory(): ConfigCacheFactoryInterface + { + $this->configCacheFactory ??= new ConfigCacheFactory($this->debug); + + return $this->configCacheFactory; + } + + private function getAllMessages(MessageCatalogueInterface $catalogue): array + { + $allMessages = []; + + foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $allMessages[$domain.MessageCatalogue::INTL_DOMAIN_SUFFIX] = $intlMessages; + $messages = array_diff_key($messages, $intlMessages); + } + if ($messages) { + $allMessages[$domain] = $messages; + } + } + + return $allMessages; + } +} diff --git a/libraries/Symfony/Component/Translation/TranslatorBag.php b/libraries/Symfony/Component/Translation/TranslatorBag.php new file mode 100644 index 00000000000..936c356bdaa --- /dev/null +++ b/libraries/Symfony/Component/Translation/TranslatorBag.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use EDD\Vendor\Symfony\Component\Translation\Catalogue\AbstractOperation; +use EDD\Vendor\Symfony\Component\Translation\Catalogue\TargetOperation; + +final class TranslatorBag implements TranslatorBagInterface +{ + /** @var MessageCatalogue[] */ + private array $catalogues = []; + + public function addCatalogue(MessageCatalogue $catalogue): void + { + if (null !== $existingCatalogue = $this->getCatalogue($catalogue->getLocale())) { + $catalogue->addCatalogue($existingCatalogue); + } + + $this->catalogues[$catalogue->getLocale()] = $catalogue; + } + + public function addBag(TranslatorBagInterface $bag): void + { + foreach ($bag->getCatalogues() as $catalogue) { + $this->addCatalogue($catalogue); + } + } + + public function getCatalogue(?string $locale = null): MessageCatalogueInterface + { + if (null === $locale || !isset($this->catalogues[$locale])) { + $this->catalogues[$locale] = new MessageCatalogue($locale); + } + + return $this->catalogues[$locale]; + } + + public function getCatalogues(): array + { + return array_values($this->catalogues); + } + + public function diff(TranslatorBagInterface $diffBag): self + { + $diff = new self(); + + foreach ($this->catalogues as $locale => $catalogue) { + if (null === $diffCatalogue = $diffBag->getCatalogue($locale)) { + $diff->addCatalogue($catalogue); + + continue; + } + + $operation = new TargetOperation($diffCatalogue, $catalogue); + $operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::NEW_BATCH); + $newCatalogue = new MessageCatalogue($locale); + + foreach ($catalogue->getDomains() as $domain) { + $newCatalogue->add($operation->getNewMessages($domain), $domain); + } + + $diff->addCatalogue($newCatalogue); + } + + return $diff; + } + + public function intersect(TranslatorBagInterface $intersectBag): self + { + $diff = new self(); + + foreach ($this->catalogues as $locale => $catalogue) { + if (null === $intersectCatalogue = $intersectBag->getCatalogue($locale)) { + continue; + } + + $operation = new TargetOperation($catalogue, $intersectCatalogue); + $operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::OBSOLETE_BATCH); + $obsoleteCatalogue = new MessageCatalogue($locale); + + foreach ($operation->getDomains() as $domain) { + $obsoleteCatalogue->add( + array_diff($operation->getMessages($domain), $operation->getNewMessages($domain)), + $domain + ); + } + + $diff->addCatalogue($obsoleteCatalogue); + } + + return $diff; + } +} diff --git a/libraries/Symfony/Component/Translation/TranslatorBagInterface.php b/libraries/Symfony/Component/Translation/TranslatorBagInterface.php new file mode 100644 index 00000000000..a87ee903b76 --- /dev/null +++ b/libraries/Symfony/Component/Translation/TranslatorBagInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * @author Abdellatif Ait boudad + */ +interface TranslatorBagInterface +{ + /** + * Gets the catalogue by locale. + * + * @param string|null $locale The locale or null to use the default + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function getCatalogue(?string $locale = null): MessageCatalogueInterface; + + /** + * Returns all catalogues of the instance. + * + * @return MessageCatalogueInterface[] + */ + public function getCatalogues(): array; +} diff --git a/libraries/Symfony/Component/Translation/Util/ArrayConverter.php b/libraries/Symfony/Component/Translation/Util/ArrayConverter.php new file mode 100644 index 00000000000..b192967ca45 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Util/ArrayConverter.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Util; + +/** + * ArrayConverter generates tree like structure from a message catalogue. + * e.g. this + * 'foo.bar1' => 'test1', + * 'foo.bar2' => 'test2' + * converts to follows: + * foo: + * bar1: test1 + * bar2: test2. + * + * @author Gennady Telegin + */ +class ArrayConverter +{ + /** + * Converts linear messages array to tree-like array. + * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']]. + * + * @param array $messages Linear messages array + */ + public static function expandToTree(array $messages): array + { + $tree = []; + + foreach ($messages as $id => $value) { + $referenceToElement = &self::getElementByPath($tree, self::getKeyParts($id)); + + $referenceToElement = $value; + + unset($referenceToElement); + } + + return $tree; + } + + private static function &getElementByPath(array &$tree, array $parts): mixed + { + $elem = &$tree; + $parentOfElem = null; + + foreach ($parts as $i => $part) { + if (isset($elem[$part]) && \is_string($elem[$part])) { + /* Process next case: + * 'foo': 'test1', + * 'foo.bar': 'test2' + * + * $tree['foo'] was string before we found array {bar: test2}. + * Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2'; + */ + $elem = &$elem[implode('.', \array_slice($parts, $i))]; + break; + } + + $parentOfElem = &$elem; + $elem = &$elem[$part]; + } + + if ($elem && \is_array($elem) && $parentOfElem) { + /* Process next case: + * 'foo.bar': 'test1' + * 'foo': 'test2' + * + * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`. + * Cancel treating $tree['foo'] as array and cancel back it expansion, + * e.g. make it $tree['foo.bar'] = 'test1' again. + */ + self::cancelExpand($parentOfElem, $part, $elem); + } + + return $elem; + } + + private static function cancelExpand(array &$tree, string $prefix, array $node): void + { + $prefix .= '.'; + + foreach ($node as $id => $value) { + if (\is_string($value)) { + $tree[$prefix.$id] = $value; + } else { + self::cancelExpand($tree, $prefix.$id, $value); + } + } + } + + /** + * @return string[] + */ + private static function getKeyParts(string $key): array + { + $parts = explode('.', $key); + $partsCount = \count($parts); + + $result = []; + $buffer = ''; + + foreach ($parts as $index => $part) { + if (0 === $index && '' === $part) { + $buffer = '.'; + + continue; + } + + if ($index === $partsCount - 1 && '' === $part) { + $buffer .= '.'; + $result[] = $buffer; + + continue; + } + + if (isset($parts[$index + 1]) && '' === $parts[$index + 1]) { + $buffer .= $part; + + continue; + } + + if ($buffer) { + $result[] = $buffer.$part; + $buffer = ''; + + continue; + } + + $result[] = $part; + } + + return $result; + } +} diff --git a/libraries/Symfony/Component/Translation/Util/XliffUtils.php b/libraries/Symfony/Component/Translation/Util/XliffUtils.php new file mode 100644 index 00000000000..d2be07a71f8 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Util/XliffUtils.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Util; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * Provides some utility methods for XLIFF translation files, such as validating + * their contents according to the XSD schema. + * + * @author Fabien Potencier + */ +class XliffUtils +{ + /** + * Gets xliff file version based on the root "version" attribute. + * + * Defaults to 1.2 for backwards compatibility. + * + * @throws InvalidArgumentException + */ + public static function getVersionNumber(\DOMDocument $dom): string + { + /** @var \DOMNode $xliff */ + foreach ($dom->getElementsByTagName('xliff') as $xliff) { + $version = $xliff->attributes->getNamedItem('version'); + if ($version) { + return $version->nodeValue; + } + + $namespace = $xliff->attributes->getNamedItem('xmlns'); + if ($namespace) { + if (0 !== substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) { + throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s".', $namespace)); + } + + return substr($namespace, 34); + } + } + + // Falls back to v1.2 + return '1.2'; + } + + /** + * Validates and parses the given file into a DOMDocument. + * + * @throws InvalidResourceException + */ + public static function validateSchema(\DOMDocument $dom): array + { + $xliffVersion = static::getVersionNumber($dom); + $internalErrors = libxml_use_internal_errors(true); + if ($shouldEnable = self::shouldEnableEntityLoader()) { + $disableEntities = libxml_disable_entity_loader(false); + } + try { + $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion)); + if (!$isValid) { + return self::getXmlErrors($internalErrors); + } + } finally { + if ($shouldEnable) { + libxml_disable_entity_loader($disableEntities); + } + } + + $dom->normalizeDocument(); + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return []; + } + + private static function shouldEnableEntityLoader(): bool + { + static $dom, $schema; + if (null === $dom) { + $dom = new \DOMDocument(); + $dom->loadXML(''); + + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + register_shutdown_function(static function () use ($tmpfile) { + @unlink($tmpfile); + }); + $schema = ' + + +'; + file_put_contents($tmpfile, ' + + + +'); + } + + return !@$dom->schemaValidateSource($schema); + } + + public static function getErrorsAsString(array $xmlErrors): string + { + $errorsAsString = ''; + + foreach ($xmlErrors as $error) { + $errorsAsString .= sprintf("[%s %s] %s (in %s - line %d, column %d)\n", + \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR', + $error['code'], + $error['message'], + $error['file'], + $error['line'], + $error['column'] + ); + } + + return $errorsAsString; + } + + private static function getSchema(string $xliffVersion): string + { + if ('1.2' === $xliffVersion) { + $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-1.2-transitional.xsd'); + $xmlUri = 'http://www.w3.org/2001/xml.xsd'; + } elseif ('2.0' === $xliffVersion) { + $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-2.0.xsd'); + $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; + } else { + throw new InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); + } + + return self::fixXmlLocation($schemaSource, $xmlUri); + } + + /** + * Internally changes the URI of a dependent xsd to be loaded locally. + */ + private static function fixXmlLocation(string $schemaSource, string $xmlUri): string + { + $newPath = str_replace('\\', '/', __DIR__).'/../Resources/schemas/xml.xsd'; + $parts = explode('/', $newPath); + $locationstart = 'file:///'; + if (0 === stripos($newPath, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + if ($tmpfile) { + copy($newPath, $tmpfile); + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } else { + array_shift($parts); + $locationstart = 'phar:///'; + } + } + + $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts)); + + return str_replace($xmlUri, $newPath, $schemaSource); + } + + /** + * Returns the XML errors of the internal XML parser. + */ + private static function getXmlErrors(bool $internalErrors): array + { + $errors = []; + foreach (libxml_get_errors() as $error) { + $errors[] = [ + 'level' => \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + 'code' => $error->code, + 'message' => trim($error->message), + 'file' => $error->file ?: 'n/a', + 'line' => $error->line, + 'column' => $error->column, + ]; + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $errors; + } +} diff --git a/libraries/Symfony/Component/Translation/Writer/TranslationWriter.php b/libraries/Symfony/Component/Translation/Writer/TranslationWriter.php new file mode 100644 index 00000000000..60e631e9290 --- /dev/null +++ b/libraries/Symfony/Component/Translation/Writer/TranslationWriter.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Writer; + +use EDD\Vendor\Symfony\Component\Translation\Dumper\DumperInterface; +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\Exception\RuntimeException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +class TranslationWriter implements TranslationWriterInterface +{ + /** + * @var array + */ + private array $dumpers = []; + + /** + * Adds a dumper to the writer. + * + * @return void + */ + public function addDumper(string $format, DumperInterface $dumper) + { + $this->dumpers[$format] = $dumper; + } + + /** + * Obtains the list of supported formats. + */ + public function getFormats(): array + { + return array_keys($this->dumpers); + } + + /** + * Writes translation from the catalogue according to the selected format. + * + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @return void + * + * @throws InvalidArgumentException + */ + public function write(MessageCatalogue $catalogue, string $format, array $options = []) + { + if (!isset($this->dumpers[$format])) { + throw new InvalidArgumentException(sprintf('There is no dumper associated with format "%s".', $format)); + } + + // get the right dumper + $dumper = $this->dumpers[$format]; + + if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0777, true) && !is_dir($options['path'])) { + throw new RuntimeException(sprintf('Translation Writer was not able to create directory "%s".', $options['path'])); + } + + // save + $dumper->dump($catalogue, $options); + } +} diff --git a/libraries/Symfony/Component/Translation/Writer/TranslationWriterInterface.php b/libraries/Symfony/Component/Translation/Writer/TranslationWriterInterface.php new file mode 100644 index 00000000000..e625479887d --- /dev/null +++ b/libraries/Symfony/Component/Translation/Writer/TranslationWriterInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Component\Translation\Writer; + +use EDD\Vendor\Symfony\Component\Translation\Exception\InvalidArgumentException; +use EDD\Vendor\Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +interface TranslationWriterInterface +{ + /** + * Writes translation from the catalogue according to the selected format. + * + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @return void + * + * @throws InvalidArgumentException + */ + public function write(MessageCatalogue $catalogue, string $format, array $options = []); +} diff --git a/libraries/Symfony/Component/Translation/composer.json b/libraries/Symfony/Component/Translation/composer.json new file mode 100644 index 00000000000..af6f7a3df06 --- /dev/null +++ b/libraries/Symfony/Component/Translation/composer.json @@ -0,0 +1,60 @@ +{ + "name": "symfony/translation", + "type": "library", + "description": "Provides tools to internationalize your application", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4", + "symfony/console": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "autoload": { + "files": [ "Resources/functions.php" ], + "psr-4": { "Symfony\\Component\\Translation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/libraries/Symfony/Contracts/Translation/CHANGELOG.md b/libraries/Symfony/Contracts/Translation/CHANGELOG.md new file mode 100644 index 00000000000..7932e26132d --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/libraries/Symfony/Contracts/Translation/LICENSE b/libraries/Symfony/Contracts/Translation/LICENSE new file mode 100644 index 00000000000..7536caeae80 --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libraries/Symfony/Contracts/Translation/LocaleAwareInterface.php b/libraries/Symfony/Contracts/Translation/LocaleAwareInterface.php new file mode 100644 index 00000000000..ca52e393e6e --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/LocaleAwareInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @return void + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale(string $locale); + + /** + * Returns the current locale. + */ + public function getLocale(): string; +} diff --git a/libraries/Symfony/Contracts/Translation/README.md b/libraries/Symfony/Contracts/Translation/README.md new file mode 100644 index 00000000000..b211d5849c8 --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/README.md @@ -0,0 +1,9 @@ +Symfony Translation Contracts +============================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/libraries/Symfony/Contracts/Translation/Test/TranslatorTest.php b/libraries/Symfony/Contracts/Translation/Test/TranslatorTest.php new file mode 100644 index 00000000000..1a5a5a40084 --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/Test/TranslatorTest.php @@ -0,0 +1,385 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Contracts\Translation\Test; + +use PHPUnit\Framework\TestCase; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorInterface; +use EDD\Vendor\Symfony\Contracts\Translation\TranslatorTrait; + +/** + * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms + * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms. + * + * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms. + * The mozilla code is also interesting to check for. + * + * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 + * + * The goal to cover all languages is to far fetched so this test case is smaller. + * + * @author Clemens Tolboom clemens@build2be.nl + */ +class TranslatorTest extends TestCase +{ + private string $defaultLocale; + + protected function setUp(): void + { + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + } + + protected function tearDown(): void + { + \Locale::setDefault($this->defaultLocale); + } + + public function getTranslator(): TranslatorInterface + { + return new class() implements TranslatorInterface { + use TranslatorTrait; + }; + } + + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $parameters) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, $parameters)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithExplicitLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @requires extension intl + * + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithDefaultLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithEnUsPosix($expected, $id, $number) + { + $translator = $this->getTranslator(); + $translator->setLocale('en_US_POSIX'); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testGetSetLocale() + { + $translator = $this->getTranslator(); + + $this->assertEquals('en', $translator->getLocale()); + } + + /** + * @requires extension intl + */ + public function testGetLocaleReturnsDefaultLocaleIfNotSet() + { + $translator = $this->getTranslator(); + + \Locale::setDefault('pt_BR'); + $this->assertEquals('pt_BR', $translator->getLocale()); + + \Locale::setDefault('en'); + $this->assertEquals('en', $translator->getLocale()); + } + + public static function getTransTests() + { + return [ + ['Symfony is great!', 'Symfony is great!', []], + ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], + ]; + } + + public static function getTransChoiceTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0], + ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1], + ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10], + // custom validation messages may be coded with a fixed value + ['There are 2 apples', 'There are 2 apples', 2], + ]; + } + + /** + * @dataProvider getInterval + */ + public function testInterval($expected, $number, $interval) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); + } + + public static function getInterval() + { + return [ + ['foo', 3, '{1,2, 3 ,4}'], + ['bar', 10, '{1,2, 3 ,4}'], + ['bar', 3, '[1,2]'], + ['foo', 1, '[1,2]'], + ['foo', 2, '[1,2]'], + ['bar', 1, ']1,2['], + ['bar', 2, ']1,2['], + ['foo', log(0), '[-Inf,2['], + ['foo', -log(0), '[-2,+Inf]'], + ]; + } + + /** + * @dataProvider getChooseTests + */ + public function testChoose($expected, $id, $number, $locale = null) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number], null, $locale)); + } + + public function testReturnMessageIfExactlyOneStandardRuleIsGiven() + { + $translator = $this->getTranslator(); + + $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2])); + } + + /** + * @dataProvider getNonMatchingMessages + */ + public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) + { + $translator = $this->getTranslator(); + + $this->expectException(\InvalidArgumentException::class); + + $translator->trans($id, ['%count%' => $number]); + } + + public static function getNonMatchingMessages() + { + return [ + ['{0} There are no apples|{1} There is one apple', 2], + ['{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['{1} There is one apple|]2,Inf] There are %count% apples', 2], + ['{0} There are no apples|There is one apple', 2], + ]; + } + + public static function getChooseTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 10 apples', 'There is one apple|There are %count% apples', 10], + + ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10], + + ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10], + + ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1], + + // Indexed only tests which are Gettext PoFile* compatible strings. + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 2 apples', 'There is one apple|There are %count% apples', 2], + + // Tests for float numbers + ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7], + ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1], + ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0], + ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + + // Test texts with new-lines + // with double-quotes and \n in id & double-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 0], + // with double-quotes and \n in id and single-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with double-quotes and id split across lines + ['This is a text with a + new-line in it. Selector = 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + // with single-quotes and id split across lines + ['This is a text with a + new-line in it. Selector > 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with single-quotes and \n in text + ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0], + // with double-quotes and id split across lines + ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1], + // escape pipe + ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0], + // Empty plural set (2 plural forms) from a .PO file + ['', '|', 1], + // Empty plural set (3 plural forms) from a .PO file + ['', '||', 1], + + // Floating values + ['1.5 liters', '%count% liter|%count% liters', 1.5], + ['1.5 litre', '%count% litre|%count% litres', 1.5, 'fr'], + + // Negative values + ['-1 degree', '%count% degree|%count% degrees', -1], + ['-1 degré', '%count% degré|%count% degrés', -1], + ['-1.5 degrees', '%count% degree|%count% degrees', -1.5], + ['-1.5 degré', '%count% degré|%count% degrés', -1.5, 'fr'], + ['-2 degrees', '%count% degree|%count% degrees', -2], + ['-2 degrés', '%count% degré|%count% degrés', -2], + ]; + } + + /** + * @dataProvider failingLangcodes + */ + public function testFailedLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix, false); + } + + /** + * @dataProvider successLangcodes + */ + public function testLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix); + } + + /** + * This array should contain all currently known langcodes. + * + * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete. + */ + public static function successLangcodes(): array + { + return [ + ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']], + ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM', 'en_US_POSIX']], + ['3', ['be', 'bs', 'cs', 'hr']], + ['4', ['cy', 'mt', 'sl']], + ['6', ['ar']], + ]; + } + + /** + * This array should be at least empty within the near future. + * + * This both depends on a complete list trying to add above as understanding + * the plural rules of the current failing languages. + * + * @return array with nplural together with langcodes + */ + public static function failingLangcodes(): array + { + return [ + ['1', ['fa']], + ['2', ['jbo']], + ['3', ['cbs']], + ['4', ['gd', 'kw']], + ['5', ['ga']], + ]; + } + + /** + * We validate only on the plural coverage. Thus the real rules is not tested. + * + * @param string $nplural Plural expected + * @param array $matrix Containing langcodes and their plural index values + */ + protected function validateMatrix(string $nplural, array $matrix, bool $expectSuccess = true) + { + foreach ($matrix as $langCode => $data) { + $indexes = array_flip($data); + if ($expectSuccess) { + $this->assertCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms."); + } else { + $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } + } + } + + protected function generateTestData($langCodes) + { + $translator = new class() { + use TranslatorTrait { + getPluralizationRule as public; + } + }; + + $matrix = []; + foreach ($langCodes as $langCode) { + for ($count = 0; $count < 200; ++$count) { + $plural = $translator->getPluralizationRule($count, $langCode); + $matrix[$langCode][$count] = $plural; + } + } + + return $matrix; + } +} diff --git a/libraries/Symfony/Contracts/Translation/TranslatableInterface.php b/libraries/Symfony/Contracts/Translation/TranslatableInterface.php new file mode 100644 index 00000000000..9b12cad4b53 --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/TranslatableInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Contracts\Translation; + +/** + * @author Nicolas Grekas + */ +interface TranslatableInterface +{ + public function trans(TranslatorInterface $translator, ?string $locale = null): string; +} diff --git a/libraries/Symfony/Contracts/Translation/TranslatorInterface.php b/libraries/Symfony/Contracts/Translation/TranslatorInterface.php new file mode 100644 index 00000000000..cde7eeb1be1 --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/TranslatorInterface.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Contracts\Translation; + +/** + * @author Fabien Potencier + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * When a number is provided as a parameter named "%count%", the message is parsed for plural + * forms and a translation is chosen according to this number using the following rules: + * + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * An interval can represent a finite set of numbers: + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @see https://en.wikipedia.org/wiki/ISO_31-11 + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string; + + /** + * Returns the default locale. + */ + public function getLocale(): string; +} diff --git a/libraries/Symfony/Contracts/Translation/TranslatorTrait.php b/libraries/Symfony/Contracts/Translation/TranslatorTrait.php new file mode 100644 index 00000000000..2ea71ffec01 --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/TranslatorTrait.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Contracts\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * A trait to help implement TranslatorInterface and LocaleAwareInterface. + * + * @author Fabien Potencier + */ +trait TranslatorTrait +{ + private ?string $locale = null; + + /** + * @return void + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + } + + public function getLocale(): string + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + if (null === $id || '' === $id) { + return ''; + } + + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { + return strtr($id, $parameters); + } + + $number = (float) $parameters['%count%']; + $locale = $locale ?: $this->getLocale(); + + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + // try to match an explicit rule, then fallback to the standard ones + if (preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (explode(',', $matches[3]) as $n) { + if ($number == $n) { + return strtr($matches['message'], $parameters); + } + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left']; + $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF; + + if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ) { + return strtr($matches['message'], $parameters); + } + } + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + $position = $this->getPluralizationRule($number, $locale); + + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + return strtr($standardRules[0], $parameters); + } + + $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); + + if (class_exists(InvalidArgumentException::class)) { + throw new InvalidArgumentException($message); + } + + throw new \InvalidArgumentException($message); + } + + return strtr($standardRules[$position], $parameters); + } + + /** + * Returns the plural position to use for the given locale and number. + * + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + private function getPluralizationRule(float $number, string $locale): int + { + $number = abs($number); + + return match ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { + 'af', + 'bn', + 'bg', + 'ca', + 'da', + 'de', + 'el', + 'en', + 'en_US_POSIX', + 'eo', + 'es', + 'et', + 'eu', + 'fa', + 'fi', + 'fo', + 'fur', + 'fy', + 'gl', + 'gu', + 'ha', + 'he', + 'hu', + 'is', + 'it', + 'ku', + 'lb', + 'ml', + 'mn', + 'mr', + 'nah', + 'nb', + 'ne', + 'nl', + 'nn', + 'no', + 'oc', + 'om', + 'or', + 'pa', + 'pap', + 'ps', + 'pt', + 'so', + 'sq', + 'sv', + 'sw', + 'ta', + 'te', + 'tk', + 'ur', + 'zu' => (1 == $number) ? 0 : 1, + 'am', + 'bh', + 'fil', + 'fr', + 'gun', + 'hi', + 'hy', + 'ln', + 'mg', + 'nso', + 'pt_BR', + 'ti', + 'wa' => ($number < 2) ? 0 : 1, + 'be', + 'bs', + 'hr', + 'ru', + 'sh', + 'sr', + 'uk' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2), + 'cs', + 'sk' => (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2), + 'ga' => (1 == $number) ? 0 : ((2 == $number) ? 1 : 2), + 'lt' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2), + 'sl' => (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)), + 'mk' => (1 == $number % 10) ? 0 : 1, + 'mt' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)), + 'lv' => (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2), + 'pl' => (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2), + 'cy' => (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)), + 'ro' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2), + 'ar' => (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))), + default => 0, + }; + } +} diff --git a/libraries/Symfony/Contracts/Translation/composer.json b/libraries/Symfony/Contracts/Translation/composer.json new file mode 100644 index 00000000000..181651e0d9a --- /dev/null +++ b/libraries/Symfony/Contracts/Translation/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/translation-contracts", + "type": "library", + "description": "Generic abstractions related to translation", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Translation\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/libraries/Symfony/Polyfill/Mbstring/LICENSE b/libraries/Symfony/Polyfill/Mbstring/LICENSE new file mode 100644 index 00000000000..6e3afce692a --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libraries/Symfony/Polyfill/Mbstring/Mbstring.php b/libraries/Symfony/Polyfill/Mbstring/Mbstring.php new file mode 100644 index 00000000000..da3b9156185 --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/Mbstring.php @@ -0,0 +1,1045 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace EDD\Vendor\Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * - mb_ucfirst - Make a string's first character uppercase + * - mb_lcfirst - Make a string's first character lowercase + * - mb_trim - Strip whitespace (or other characters) from the beginning and end of a string + * - mb_ltrim - Strip whitespace (or other characters) from the beginning of a string + * - mb_rtrim - Strip whitespace (or other characters) from the end of a string + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const SIMPLE_CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($s)) { + $r = []; + foreach ($s as $str) { + $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding); + } + + return $r; + } + + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + static $caseFolding = null; + if (null === $caseFolding) { + $caseFolding = self::getData('caseFolding'); + } + $s = strtr($s, $caseFolding); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + if (!\is_array($var)) { + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + foreach ($var as $key => $value) { + if (!self::mb_check_encoding($key, $encoding)) { + return false; + } + if (!self::mb_check_encoding($value, $encoding)) { + return false; + } + } + + return true; + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ + self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), + self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), + ]); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); + $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); + + $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); + $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string + { + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given'); + } + + if (self::mb_strlen($pad_string, $encoding) <= 0) { + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + + $paddingRequired = $length - self::mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + case \STR_PAD_RIGHT: + return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + + public static function mb_ucfirst(string $string, ?string $encoding = null): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given'); + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + public static function mb_lcfirst(string $string, ?string $encoding = null): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given'); + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } + + public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string + { + return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + + public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string + { + return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); + } + + public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string + { + return self::mb_internal_trim('{[%s]+$}D', $string, $characters, $encoding, __FUNCTION__); + } + + private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given'); + } + + if ('' === $characters) { + return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding); + } + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $string)) { + $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string); + } + if (null !== $characters && !preg_match('//u', $characters)) { + $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters); + } + } else { + $string = iconv($encoding, 'UTF-8//IGNORE', $string); + + if (null !== $characters) { + $characters = iconv($encoding, 'UTF-8//IGNORE', $characters); + } + } + + if (null === $characters) { + $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"; + } else { + $characters = preg_quote($characters); + } + + $string = preg_replace(sprintf($regex, $characters), '', $string); + + if (null === $encoding) { + return $string; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $string); + } + + private static function assertEncoding(string $encoding, string $errorFormat): void + { + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(sprintf($errorFormat, $encoding)); + } + + // BC for PHP 7.3 and lower + if (!$validEncoding) { + throw new \ValueError(sprintf($errorFormat, $encoding)); + } + } +} diff --git a/libraries/Symfony/Polyfill/Mbstring/README.md b/libraries/Symfony/Polyfill/Mbstring/README.md new file mode 100644 index 00000000000..478b40da25e --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/caseFolding.php b/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/caseFolding.php new file mode 100644 index 00000000000..512bba0bfde --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/caseFolding.php @@ -0,0 +1,119 @@ + 'i̇', + 'µ' => 'μ', + 'ſ' => 's', + 'ͅ' => 'ι', + 'ς' => 'σ', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϵ' => 'ε', + 'ẛ' => 'ṡ', + 'ι' => 'ι', + 'ß' => 'ss', + 'ʼn' => 'ʼn', + 'ǰ' => 'ǰ', + 'ΐ' => 'ΐ', + 'ΰ' => 'ΰ', + 'և' => 'եւ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẚ' => 'aʾ', + 'ẞ' => 'ss', + 'ὐ' => 'ὐ', + 'ὒ' => 'ὒ', + 'ὔ' => 'ὔ', + 'ὖ' => 'ὖ', + 'ᾀ' => 'ἀι', + 'ᾁ' => 'ἁι', + 'ᾂ' => 'ἂι', + 'ᾃ' => 'ἃι', + 'ᾄ' => 'ἄι', + 'ᾅ' => 'ἅι', + 'ᾆ' => 'ἆι', + 'ᾇ' => 'ἇι', + 'ᾈ' => 'ἀι', + 'ᾉ' => 'ἁι', + 'ᾊ' => 'ἂι', + 'ᾋ' => 'ἃι', + 'ᾌ' => 'ἄι', + 'ᾍ' => 'ἅι', + 'ᾎ' => 'ἆι', + 'ᾏ' => 'ἇι', + 'ᾐ' => 'ἠι', + 'ᾑ' => 'ἡι', + 'ᾒ' => 'ἢι', + 'ᾓ' => 'ἣι', + 'ᾔ' => 'ἤι', + 'ᾕ' => 'ἥι', + 'ᾖ' => 'ἦι', + 'ᾗ' => 'ἧι', + 'ᾘ' => 'ἠι', + 'ᾙ' => 'ἡι', + 'ᾚ' => 'ἢι', + 'ᾛ' => 'ἣι', + 'ᾜ' => 'ἤι', + 'ᾝ' => 'ἥι', + 'ᾞ' => 'ἦι', + 'ᾟ' => 'ἧι', + 'ᾠ' => 'ὠι', + 'ᾡ' => 'ὡι', + 'ᾢ' => 'ὢι', + 'ᾣ' => 'ὣι', + 'ᾤ' => 'ὤι', + 'ᾥ' => 'ὥι', + 'ᾦ' => 'ὦι', + 'ᾧ' => 'ὧι', + 'ᾨ' => 'ὠι', + 'ᾩ' => 'ὡι', + 'ᾪ' => 'ὢι', + 'ᾫ' => 'ὣι', + 'ᾬ' => 'ὤι', + 'ᾭ' => 'ὥι', + 'ᾮ' => 'ὦι', + 'ᾯ' => 'ὧι', + 'ᾲ' => 'ὰι', + 'ᾳ' => 'αι', + 'ᾴ' => 'άι', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾶι', + 'ᾼ' => 'αι', + 'ῂ' => 'ὴι', + 'ῃ' => 'ηι', + 'ῄ' => 'ήι', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῆι', + 'ῌ' => 'ηι', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'ῲ' => 'ὼι', + 'ῳ' => 'ωι', + 'ῴ' => 'ώι', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῶι', + 'ῼ' => 'ωι', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', +]; diff --git a/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/lowerCase.php b/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 00000000000..fac60b081a1 --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/titleCaseRegexp.php b/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 00000000000..2a8f6e73b99 --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/libraries/Symfony/Polyfill/Mbstring/bootstrap.php b/libraries/Symfony/Polyfill/Mbstring/bootstrap.php new file mode 100644 index 00000000000..bb1d2becefb --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/bootstrap.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); } +} + +if (!function_exists('mb_trim')) { + function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); } +} + + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/libraries/Symfony/Polyfill/Mbstring/bootstrap80.php b/libraries/Symfony/Polyfill/Mbstring/bootstrap80.php new file mode 100644 index 00000000000..66577496f8b --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/bootstrap80.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use EDD\Vendor\Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false|null { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); } +} + +if (!function_exists('mb_trim')) { + function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/libraries/Symfony/Polyfill/Mbstring/composer.json b/libraries/Symfony/Polyfill/Mbstring/composer.json new file mode 100644 index 00000000000..4ed241a33b5 --- /dev/null +++ b/libraries/Symfony/Polyfill/Mbstring/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/license.txt b/license.txt old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..dc42d185fc0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,24711 @@ +{ + "name": "easy-digital-downloads", + "version": "3.2.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "easy-digital-downloads", + "version": "3.2.8", + "hasInstallScript": true, + "license": "GPL-2.0-or-later", + "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", + "lodash.foreach": "^4.5.0", + "micromodal": "^0.4.10", + "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" + }, + "engines": { + "node": ">=10.0.0", + "npm": ">=6.9.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/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/code-frame/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/code-frame/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/code-frame/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/code-frame/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/code-frame/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/code-frame/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/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.23.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", + "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "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.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "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.23.3", + "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.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "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.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "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.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "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-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "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" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", + "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.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.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@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-transform-for-of": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@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-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", + "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@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-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@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-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@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-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.23.3.tgz", + "integrity": "sha512-zP0QKq/p6O42OL94udMgSfKXyse4RyJ0JqbQ34zDAONWjyrEsghYEyTSK5FIpmXmCpB55SHokL1cRRKHv8L2Qw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", + "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz", + "integrity": "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz", + "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", + "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@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.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@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-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.8", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.23.3.tgz", + "integrity": "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-react-display-name": "^7.23.3", + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "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.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "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/transform/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/@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.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "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/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "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.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "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/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/@npmcli/fs/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/fs/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/@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==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "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.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/cheerio": { + "version": "0.22.35", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz", + "integrity": "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==", + "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.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "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.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "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.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "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.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true + }, + "node_modules/@types/q": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", + "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "16.14.56", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.56.tgz", + "integrity": "sha512-MxuHB7dvVm5yOxRr7hJoonLG0JY8YvqZtaQ9Quirp3Oe4FLFjAgxkxsKE6IspdHPpRVZKo2ZoDEravWO81EeYA==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "16.9.24", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.24.tgz", + "integrity": "sha512-Gcmq2JTDheyWn/1eteqyzzWKSqDjYU6KYsIvH7thb7CR5OYInAWOX+7WnKf6PaU/cbdOc4szJItcDEJO7UGmfA==", + "dev": true, + "dependencies": { + "@types/react": "^16" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, + "node_modules/@types/source-list-map": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.6.tgz", + "integrity": "sha512-5JcVt1u5HDmlXkwOD2nslZVllBBc7HDuOICfiZah2Z0is8M8g+ddAEawbmd3VjedfDHBzxCaXLs07QEmb7y54g==", + "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.12", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.12.tgz", + "integrity": "sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==", + "dev": true + }, + "node_modules/@types/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-Hm/T0kV3ywpJyMGNbsItdivRhYNCQQf1IIsYsXnoVPES4t+FMLyDe0/K+Ea7ahWtMtSNb22ZdY7MIyoD9rqARg==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", + "dev": true + }, + "node_modules/@types/webpack": { + "version": "4.41.38", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.38.tgz", + "integrity": "sha512-oOW7E931XJU1mVfCnxCVgv8GLFL768pDO5u2Gzk82i8yTIgX6i7cntyZOkZYb/JtYM8252SN9bQp9tgkVDSsRw==", + "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.3", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==", + "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.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "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/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/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@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/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/@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/scripts/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/@wordpress/scripts/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/@wordpress/scripts/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/@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==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "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": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "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-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.2.tgz", + "integrity": "sha512-gUHx76KtnhEgB3HOuFYiCm3FIdEs6ocM2asHvNTkfu/Y09qQVrrVVaOKENmS2KkSaGoxgXNqC+ZVtR/n0MOkSA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "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.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "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.2", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.2.tgz", + "integrity": "sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "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.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "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.6", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.6.tgz", + "integrity": "sha512-UW+Mz8LG/sPSU8jRDCjVr6J/ZKAGpHfwrZ6kWTG5qCxIEiXdVshqGnu5vEZA8S1y6X4aCSbQZ0/EEsfvEvBiSg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "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.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.1.tgz", + "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", + "dev": true, + "dependencies": { + "object.assign": "^4.1.4", + "util": "^0.10.4" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "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.6", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "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/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "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/available-typed-arrays": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", + "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true + }, + "node_modules/axe-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", + "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "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.4.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", + "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.5.0", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.5.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.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/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/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.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.4", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "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.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "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": "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/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/cacache/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/cacache/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/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.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "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.30001587", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", + "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "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.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "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-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "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.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "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.36.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz", + "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz", + "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==", + "dev": true, + "dependencies": { + "browserslist": "^4.22.3" + }, + "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/crc/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/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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "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.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.2.tgz", + "integrity": "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==", + "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": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "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.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "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/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "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==", + "deprecated": "Use your platform's native DOMException instead", + "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.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "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.672", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.672.tgz", + "integrity": "sha512-YYCy+goe3UqZqa3MOQCI5Mx/6HdBLzXL/mkbGCEWL3sP3Z1BP9zqAzeD3YEmLZlespYGFtyM8tRp5i2vfaUGCA==", + "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.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "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.8", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.8.tgz", + "integrity": "sha512-uYGC31eGZBp5nGsr4nKhZKvxGQjyHGjS06BJsUlWgE29/hvnpgCsT1BJvnnyny7N3GIIVyxZ4O9GChr6hy2WQA==", + "dev": true, + "dependencies": { + "enzyme-adapter-utils": "^1.14.2", + "enzyme-shallow-equal": "^1.0.7", + "hasown": "^2.0.0", + "object.assign": "^4.1.5", + "object.values": "^1.1.7", + "prop-types": "^15.8.1", + "react-is": "^16.13.1", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.7.2" + }, + "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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/enzyme-adapter-utils": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.2.tgz", + "integrity": "sha512-1ZC++RlsYRaiOWE5NRaF5OgsMt7F5rn/VuaJIgc7eW/fmgg8eS1/Ut7EugSPPi7VMdWMLcymRnMF+mJUJ4B8KA==", + "dev": true, + "dependencies": { + "airbnb-prop-types": "^2.16.0", + "function.prototype.name": "^1.1.6", + "hasown": "^2.0.0", + "object.assign": "^4.1.5", + "object.fromentries": "^2.0.7", + "prop-types": "^15.8.1", + "semver": "^6.3.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-shallow-equal": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.7.tgz", + "integrity": "sha512-/um0GFqUXnpM9SvKtje+9Tjoz3f1fpBC3eXRFrNs8kpYn69JljciYP7KZTqM/YQbUY9KUjvKB4jo/q+L6WGGvg==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0", + "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.22.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", + "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "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-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.1", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" + }, + "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-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", + "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.4", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "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/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/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-jsdoc/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/eslint-plugin-jsx-a11y": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", + "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" + }, + "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.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", + "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.4", + "semver": "^6.3.1", + "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.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.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.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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/eslint/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "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/eslint/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/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.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "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-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "peer": true, + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/extract-zip/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, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/extract-zip/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, + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "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.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "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.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "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==", + "deprecated": "This module is no longer supported.", + "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-file-up/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/find-file-up/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/find-file-up/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/find-file-up/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/find-file-up/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/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": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "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-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "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.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "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.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "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": "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/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/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/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "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/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "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.6.1", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", + "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", + "dev": true, + "dependencies": { + "dateformat": "~4.6.2", + "eventemitter2": "~0.4.13", + "exit": "~0.1.2", + "findup-sync": "~5.0.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.6.3", + "js-yaml": "~3.14.0", + "minimatch": "~3.0.4", + "nopt": "~3.0.6" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=16" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "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.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "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.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "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.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "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.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "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/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "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.3", + "domutils": "^3.0.1", + "entities": "^4.4.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.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "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.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "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.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "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.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "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.1", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "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-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "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.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "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.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/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/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/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/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.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "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.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "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.20.0", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.20.0.tgz", + "integrity": "sha512-c8tg4ltIIP6L7l0bZ79sRzOJYquyjS48kQZ6iv8MJ2r0OYztxtkWYKTReZyU2/zVFYiINB29i0Z/IRNNuJQN1g==", + "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-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "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.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "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.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "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.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "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/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.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", + "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": "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/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/micromodal": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.4.10.tgz", + "integrity": "sha512-BUrEnzMPFBwK8nOE4xUDYHLrlGlLULQVjpja99tpJQPSUEWgw3kTLp1n1qv0HmKU29AiHE7Y7sMLiRziDK4ghQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "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/minipass/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/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/minizlib/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/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.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "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/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.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "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.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-package-json-lint/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/npm-package-json-lint/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/npm-package-json-lint/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-package-json-lint/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/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.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "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-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "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.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "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.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.7.tgz", + "integrity": "sha512-PrJz0C2xJ58FNn11XV2lr4Jt5Gzl94qpy9Lu0JlfEj14z88sqbSBJCBEzdlNUCzY2gburhbrwOZ5BHCmuNUy0g==", + "dev": true, + "dependencies": { + "array.prototype.reduce": "^1.0.6", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "safe-array-concat": "^1.0.0" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "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.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "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.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "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.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "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.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "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": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "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.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-2.1.1.tgz", + "integrity": "sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg==", + "deprecated": "< 21.5.0 is no longer supported", + "dev": true, + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@types/mime-types": "^2.1.0", + "debug": "^4.1.0", + "extract-zip": "^1.6.6", + "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": "^2.6.1", + "ws": "^6.1.0" + }, + "engines": { + "node": ">=8.16.0" + } + }, + "node_modules/puppeteer/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/puppeteer/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, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "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-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/raw-body/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/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.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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.getprototypeof": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", + "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0", + "get-intrinsic": "^1.2.3", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "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.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "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.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "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/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.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.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": "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/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-array-concat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "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.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "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.71.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", + "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", + "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": ">=14.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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "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-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "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-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "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.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "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.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "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-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "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.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "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.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "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/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/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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "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.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "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/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/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.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "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.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.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/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/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.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "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/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/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/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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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==", + "deprecated": "Use String.prototype.trim() instead", + "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/typed-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz", + "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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/unbzip2-stream/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/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.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "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.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "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.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "dev": true, + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/url/node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "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/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/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": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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-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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "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.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "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/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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "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/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-encoding/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/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-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "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/which-typed-array": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "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": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000000..17ba3fd3915 --- /dev/null +++ b/package.json @@ -0,0 +1,80 @@ +{ + "name": "easy-digital-downloads", + "version": "3.3.7", + "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", + "lodash.foreach": "^4.5.0", + "micromodal": "^0.4.10", + "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 build:blocks && npm run translate && grunt build && npm run translate:lite", + "build:blocks": "cd includes/blocks && npm run build && cd ../../", + "dev": "cross-env NODE_ENV=production wp-scripts start --webpack-no-externals", + "lite": "grunt lite && npm run translate:lite && grunt compress:lite", + "local": "grunt build && npm run translate:lite && grunt compress", + "package": "npm run build && grunt compress", + "postinstall": "cd includes/blocks && npm install && cd ../../", + "pro": "grunt pro", + "repo": "grunt repo && npm run translate:repo", + "test:unit": "wp-scripts test-unit-js --config tests/jest/jest.config.js", + "translate": "grunt clean && wp i18n make-pot . languages/easy-digital-downloads.pot --exclude=node_modules,assets,tests,libraries --ignore-domain", + "translate:lite": "cd build/easy-digital-downloads && wp i18n make-pot . languages/easy-digital-downloads.pot --exclude=assets,libraries --ignore-domain && cd ../../", + "translate:repo": "cd build/easy-digital-downloads-public && wp i18n make-pot . languages/easy-digital-downloads.pot --exclude=assets,libraries --ignore-domain && cd ../../", + "update": "composer update --ignore-platform-reqs && composer run mozart" + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 00000000000..b0790697128 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + easy-digital-downloads.php + i18n + includes + src + templates + + */assets/css/* + */assets/js/* + */assets/pro/* + */bin/* + */build/* + */languages/* + */libraries/* + */tests/* + */vendor/* + diff --git a/phpunit-printer.yml b/phpunit-printer.yml new file mode 100644 index 00000000000..63d579b0a35 --- /dev/null +++ b/phpunit-printer.yml @@ -0,0 +1,13 @@ +options: + cd-printer-hide-class: false + cd-printer-simple-output: false + cd-printer-show-config: false + cd-printer-hide-namespace: false + cd-printer-dont-format-classname: false +markers: + cd-pass: "✔ " + cd-fail: "✖ " + cd-error: "⚈ " + cd-skipped: "⇢ " + cd-incomplete: "∅ " + cd-risky: "⌽ " diff --git a/phpunit.xml b/phpunit.xml new file mode 100755 index 00000000000..b8de6720fd4 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,55 @@ + + + + + ./tests + + + + + ./easy-digital-downloads.php + ./uninstall.php + ./includes + ./src + ./i18n + + ./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 + ./libraries + ./vendor + ./templates + ./includes/admin/reporting/export + ./includes/admin/reporting/views + ./includes/admin/views + ./includes/blocks/views + + + + + + edd_ajax + + + diff --git a/readme.md b/readme.md new file mode 100755 index 00000000000..1a5872effe4 --- /dev/null +++ b/readme.md @@ -0,0 +1,51 @@ +# [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) [![License](https://img.shields.io/badge/license-GPL--2.0%2B-blue.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) + +## Build Scripts +EDD has multiple scripts to prepare installable packages: +* `build`: Generate the final files for a release. This script regenerates all CSS/JS, runs translations, and copies files to a build directory. This does not generate zip files. +* `build:blocks`: This is for internal use only and rebuilds the blocks asset files. +* `dev`: Watch and rebuild CSS/JS files; this will watch for changes and automatically rebuild assets. +* `lite`: Generate an `easy-digital-downloads-x.x.x.zip` file, the lite version of EDD. Does not rebuild assets. Useful for quickly creating an installable package. +* `local`: Generate zip files for both lite and pro. Does not rebuild assets. +* `package`: Rebuild assets, run translations, copy files, and compress files for installation on any WordPress site, for both lite and pro versions of EDD. +* `postinstall`: Runs automatically after `npm install`; do not use directly. +* `pro`: Generate an `easy-digital-downloads-pro-x.x.x.zip` file, the pro version of EDD. Does not rebuild assets. Useful for quickly creating an installable package. +* `repo`: Generate a directory of files to copy over to the public repository. Updates the translation. +* `test:unit`: Run JS unit tests. +* `translate`: Translate EDD (Pro). +* `translate:lite`: Translate the public version of EDD. Runs as part of `npm run lite`; generally do not run this directly. +* `translate:repo`: Translate the repository copy of EDD. Runs as part of `npm run repo`; generally do not run this directly. +* `update`: Updates all Composer packages and runs the Mozart script. Only run this if a Composer library needs to be updated. diff --git a/readme.txt b/readme.txt index c1098de4d05..712988919e9 100755 --- a/readme.txt +++ b/readme.txt @@ -1,1156 +1,609 @@ -=== 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 – eCommerce Payments and Subscriptions made easy === +Author URI: https://easydigitaldownloads.com +Plugin URI: https://easydigitaldownloads.com +Contributors: easydigitaldownloads, am, cklosows, littlerchicken, achchu93, smub, chriscct7, sumobi, SpencerFinnell, johnjamesjacoby +Tags: ecommerce, payments, sell, digital store, stripe +Requires at least: 6.2 +Tested up to: 6.7 +Requires PHP: 7.4 +Stable Tag: 3.3.7 +License: GPLv2 or later + +The #1 eCommerce plugin to sell digital products & subscriptions. Accept credit card payments with Stripe & PayPal and start your store today. +== Description == -Sell digital downloads through WordPress with this complete digital downloads management plugin += eCommerce without limits = -== Description == +We believe that _your passion_ for creating shouldn't be limited by your choice of ecommerce software. **Easy Digital Downloads** gives you unlimited products with no hidden listing fees, unlimited transactions, and provides unlimited possibilities. + +Too often, you must choose between powerful and cheap. Not with Easy Digital Downloads. It's free and easy to start with limitless possibilities, which is why some of the most popular digital ecommerce businesses trust Easy Digital Downloads with their store. + +Within minutes of starting the Setup Wizard, you'll have access to all the features you need to start selling your digital products today, including an integrated shopping cart, flexible payments (like credit cards, PayPal, Apple Pay, and Google Pay), a clean and optimized checkout, customer management, detailed ecommerce reports, secure file storage, discount codes, and more. + +> Easy Digital Downloads Pro
    +> This plugin is the lite version of the Easy Digital Downloads Pro plugin that comes with all the ecommerce features you will ever need, including subscriptions, custom payment forms, email marketing tools, additional payment processors, and more. [Purchase the best premium WordPress ecommerce solution now!](https://easydigitaldownloads.com/?utm_source=extensions&utm_medium=description_tab&utm_content=extensions&utm_campaign=readme) + +[youtube https://www.youtube.com/watch?v=kUEdc8sB3uQ] + += Secure payments with a checkout form = + +With 1-Click connections to the most popular payment processors, you'll be selling within minutes, allowing your customers to checkout with ease with the ability to choose from the most popular payment methods in their region of the world. With Stripe and PayPal, your ecommerce store is ready for global payments. + +Unlike some eCommerce solutions, all of our official payment integrations securely process payments directly between your store and the payment processor. + +> What can I say? Stripe is my go-to payment processor, and without [EDD] I don't know what I would do. Connect EDD to your Stripe account in seconds, and you're done! +> Matt Whiteley - Whiteley Designs + +Easy Digital Downloads is a verified member of the [Stripe](https://stripe.com/partners/directory/easy-digital-downloads) and PayPal partner programs, so you can process payments with confidence, knowing that EDD meets their strict quality standards. + +Support payments globally with the following payment methods, immediately available: + +* Credit Card Payments +* Apple Pay +* Google Pay +* WeChat Pay +* AliPay +* Amazon Pay +* GrabPay +* Bancontact +* EPS +* giropay +* iDEAL +* Przelewy24 +* PayPal +* PayPal Credit +* CashApp +* Link by Stripe +* Venmo +* Sofort +* and more! + +With our smart payment method integrations, you get access to most of the latest payment methods as soon as they are available, without any code changes or updates. + += Make decisions with powerful reporting = +Unlock the full potential of your digital store with Easy Digital Downloads — the best WordPress ecommerce solution renowned for its powerful reporting features. Dive deep into the heart of your digital business with tools designed not just to give you insights into what is happening in your business now, but also to help you make decisions that will help you grow your business. -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. +With the included date comparison tools, you can monitor trends in your sales, revenue, customers, and other data from one period to another, giving you the insights to make informed decisions and accelerate your growth. Some of the data points you get access to include: -**Follow this plugin on [Git Hub](https://github.com/pippinsplugins/Easy-Digital-Downloads)** +* Customer growth +* Revenue and Sales +* Filtering by product +* Detailed category and tag reports +* Revenue and Sales by Country +* Revenue and Sales by Payment Method +* Recurring Revenue Reports (Requires Pro) -Features of the plugin include: += Full Easy Digital Downloads Feature List = -* 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 +* Responsive Checkout Form - With the included Checkout Block, you get a responsive and customizable checkout form that is sure to convert. +* Digital Product Management - Create as many digital products as you want, with no added costs. Create single products, product variations, and product bundles. +* Discount Codes - Run sales and promotions with the built-in discount code management. Set start and expiration dates, product limitations, price limitations, and reward your best customers with custom discount codes. +* eCommerce Reports - Easy Digital Downloads provides the best reporting available in WordPress ecommerce with the included reporting features. You'll get access to valuable insights into the growth of your ecommerce business. +* 1-Click Payments - Go from Install to Selling in a matter of minutes with our official integrations with Stripe and PayPal. +* Flexible Payment Methods - With the included integrations for Stripe and PayPal, it's even easier for you to collect payments and sell access to your digital products. +* File Downloads Protection - Your downloadable products are safe with Easy Digital Downloads. When a user purchases your product, we generate a secure and unique link that allows only your customers to download your files from your ecommerce site. +* Email Receipts - The included email receipts are fully customizable to ensure that your brand is represented in your receipts. +* Customer Management - Track your customer lifetime value and purchase history with the included Customer Management system. Quickly access a customer's entire purchase history, add notes, and link to WordPress users. +* Refund and Dispute Handling - With our automated order management tools, EDD ensures that your records are accurate and customers only have access to files when they should. +* Customer Profile Editor - Give your users to their purchase history and profile management with the included blocks. -More information at [Easy Digital Downloads.com](http://easydigitaldownloads.com/). +Extensible, adaptable, and open source -- Easy Digital Downloads is created with designers and developers in mind. With our extensive list of integrations as well as the included hooks and filters, the possibilities are endless. -[youtube http://www.youtube.com/watch?v=SjOeSZ08_IA] +We've made it even easier, with [1-Click installation of code snippets via WPCode](https://library.wpcode.com/profile/easydigitaldownloads/). -**Sell and Track Software License Keys** +> 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 -[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. += Grow revenue by going Pro = -Includes a complete integration pack for delivering automatic upgrades for premium WordPress plugins and themes. +With our Pro plans 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: -**Add an Affiliate System to Your Store** +* [Subscriptions](https://easydigitaldownloads.com/downloads/recurring-payments/?utm_source=extension&utm_medium=description_tab&utm_content=recurring-payments&utm_campaign=readme) - Build a sustainable ecommerce business and add recurring revenue to your store by selling subscriptions. +* [Reviews](https://easydigitaldownloads.com/downloads/product-reviews/?utm_source=extension&utm_medium=description_tab&utm_content=reviews&utm_campaign=readme) - What better way promote your digital products than by having your happy customers do it for you! Schedule automatic review requests, show average ratings and rating breakdowns. +* [Content Restriction](https://easydigitaldownloads.com/downloads/content-restriction/?utm_source=extension&utm_medium=description_tab&utm_content=content-restriction&utm_campaign=readme) - Restrict content on your site and only share it with customers who have purchased specific products. Content Restriction is great for membership sites and customer-only information. +* [Software Licenses](https://easydigitaldownloads.com/downloads/software-licensing/?utm_source=extension&utm_medium=description_tab&utm_content=software-licensing&utm_campaign=readme) - Sell activatable license keys for software products, including WordPress Themes/Plugins, desktop software, SaaS solutions, and more. +* [Frontend Submissions](https://easydigitaldownloads.com/downloads/frontend-submissions/?utm_source=extension&utm_medium=description_tab&utm_content=frontend-submissions&utm_campaign=readme) - Build a full-featured vendor marketplace and grow a community of creators. +* [Commissions](https://easydigitaldownloads.com/downloads/commissions/?utm_source=extension&utm_medium=description_tab&utm_content=commissions&utm_campaign=readme) - Pay your vendors with percentage or flat rate commissions when their products are purchased. +* [Free Downloads](https://easydigitaldownloads.com/downloads/free-downloads/?utm_source=extension&utm_medium=description_tab&utm_content=free-downloads&utm_campaign=readme) - Use free products as lead magnets to grow your email list. +* [Recommended Products](https://easydigitaldownloads.com/downloads/recommended-products/?utm_source=extension&utm_medium=description_tab&utm_content=recommended-products&utm_campaign=readme) - Add automatically generated recommendations to your products and checkout experience. -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. +> …We've got over 70+ premium features and integrations! +> These are just a few of the features available to help you optimize your digital store and sell more digital products. View our full list of [premium ecommerce features](https://easydigitaldownloads.com/lite-upgrade/?utm_source=extension&utm_medium=description_tab&utm_content=view-full&utm_campaign=readme). -**Build Up Your Email Subscribers** += Get help = -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. +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). -**Languages** += Looking to change e-Commerce solutions? = -Easy Digital Downloads as been translated into the following languages: +Easy Digital Downloads is the perfect digital e-Commerce alternative to Etsy, Gumroad, WooCommerce, Shopfiy, SureCart, BigCommerce, or Wix. -1. English -2. German -3. Spanish -4. French -5. Italian -6. Dutch -7. European Portuguese -8. Turkish -9. Arabic +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. -Would you like to help translate the plugin into more langauges? [Contact Pippin](http://easydigitaldownloads.com/contact-developer/). +If Easy Digital Downloads doesn't quite fit your needs, we suggest you try [WP Simple Pay](https://wpsimplepay.com/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_content=something-else&utm_term=description), the #1 Stripe Payments Plugin for WordPress. == 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, visit 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: += What type of products 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. -= My Payments Marked as Pending = +Easy Digital Downloads also supports selling access to software, services, memberships, and content when you [upgrade to Pro](https://easydigitaldownloads.com/pricing/?utm_source=pricing&utm_medium=faq_tab&utm_content=upgrade-to-pro&utm_campaign=readme). -There are several reasons this happens. Please follow the suggestions [here](https://easydigitaldownloads.com/documentation/faqs/). += How can I increase conversions? = -= Getting a 404 error? = +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. -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". += Is an SSL certificate required to sell digital products? = -= How do I Show the User's Purchase History? = +The security of you and your customers' data is very important, so we strongly recommend you have an SSL certificate for your store. [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) -Place the [purchase_history] short code on any page. += What themes work with Easy Digital Downloads? = -If you want to just show a list of the files the user has purchased, use the [download_history] short code instead. +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). -= Can I Setup an Affiliate System? = += Is there a sample import file I can use to setup a demo store? = -Yes! EDD has an add-on that provides a complete affiliate system that you can use to award commissions to your affiliate marketers. +Yes! Simply go to Tools > Import and install the WordPress Importer, then you can use the sample-products-import.xml file located in wp-content/plugins/easy-digital-downloads/assets/. This will create several sample products and plugin pages for you. -[Checkout Affiliates Pro + EDD Integration Pack](http://easydigitaldownloads.com/extension/affiliates-pro-integration-pack/) += Can I import all the products from my existing store into EDD? = -= Can Users Purchase Products without Using PayPal? = +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. -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: += How do I migrate from Gumroad? = -* [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 +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). -== Screenshots == += How do I show the user's purchase history? = -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 +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). += How do I display my digital products? = -== Changelog == +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. + += Can I set up an affiliate program? = + +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. + +[Check out AffiliateWP](https://affiliatewp.com?utm_source=edd_readme&utm_medium=faq_tab&utm_content=home&utm_campaign=readme) + += Can I give my customers downloadable PDF Invoices? = + +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.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 += Is selling subscriptions supported? = -= 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, 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. -* Fixed a bug with the checkout login / register forms +== Notes == -= 1.0.1.2 = +**A Message from our President** -* 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. +Since 2012, I've been helping build Easy Digital Downloads and making it the best eCommerce solution for digital creators. Now, as the President of the company, my commitment continues to be making Easy Digital Downloads the most effortless way to manage your ecommerce store, handle billing, protect your files, integrate with payment processors, and sell your digital products and services. -= 1.0.1.1 = +I'm proud of what we've built and our world-class customer support team that is here to help you succeed. I hope you enjoy using Easy Digital Downloads. -* Minor updates including inclusion of INR as an available currency. -* Updates to the default.po file for missing strings. +Thank You, +Chris Klosowski -= 1.0 = +**Branding guideline** -* First offical release! +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) +**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. + +== Screenshots == + +1. Detailed Reports and Analytics +2. Manage Easy Digital Download Customers +3. History of Payments +4. View and Manage Payment Details +5. Product Grid - Vendd +6. Checkout Form Block - Vendd +7. Product Grid - Default Theme +8. Checkout Form Block - Default Theme + +== Changelog == += 3.3.7 = +* Blocks: Fixed undefined index error in checkout block preview. +* Checkout: Improved existing email check for logged-in users during checkout. +* Compatibility: Improved handling of upgrade pages. +* Currency: Fixed Money_Formatter unformat method ensuring `original_amount` is not null before processing. +* Customers: Improve performance of viewing customers in the admin. +* Downloads: Improved file protection handling in some edge case configurations. +* Downloads: Supports feature now listens on 'change' instead of 'click'. +* Downloads: Improved variable price handling. +* Downloads: Improved download title validation before returning via AJAX. +* Emails: Fixed email summary dates not adjusting for the local time zone. +* Emails: Store admins can now search/filter emails and email logs. +* Fees: Order adjustments with titles longer than 100 characters now correctly record fees. +* HTML: `EDD\HTML\Number` input now allows a `0` minimum value. +* i18n: Fixed multiple translation warnings in the new metabox implementation. +* Orders: Orders - Deferred Actions now unschedule or gracefully fail on deleted orders. +* PayPal: Ensure that `straight to gateway` is only enabled for supported PayPal gateways. +* PayPal: Removed PayPal Commerce IE 11 Polyfills by default, now requiring opt-in. +* Reports: Improved price assignment selection when adding new price variations. +* Settings: Improved admin settings notices. +* Settings: Improved styling for input fields. +* Stripe: Fixed issue where Stripe variables were added to the DOM twice. +* Taxes: Fix an issue preventing saving a global tax rate. +* Dev: Counting with `fields` set fails in Berlin. +* Dev: Generating orders via CLI no longer adds unnecessary order meta. +* Dev: Fixed CLI order handling triggering new deprecation notices. +* Dev: Unit tests updated to remove older versions. +* Dev: Resending a receipt no longer throws a deprecation notice. + +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) + += 3.3.6.1 = +* Fixes a formatting issue for stores using European style decimal separators. + += 3.3.6 = +* **Important:** This release requires **WordPress 6.2 or higher**. +* Admin: Prevent UI shifting in the EDD admin pages. +* Blocks: Improved the UI of the Checkout block registration settings. +* Checkout: Improved reliability of guest checkout process. +* Checkout: Improved the detection of existing customer email addresses. +* Customers: Switched to using `Order` objects instead of `EDD_Payment` objects. +* Dev: Added the `edd_empty_cart_message` to the Checkout block. +* Dev: Fixed an issue that could cause hooking into `edd_purchase_form_user_info_fields` to result in a fatal error. +* Dev: Introduced the `edd_order_receipt_before` hook to the receipt block. +* Discounts: Fixed an issue that would prevent min/max values on discounts from being changed to empty values. +* Discounts: Improved the logic of discount code validation to ensure that required products are allowed even if in a prevented category. +* Downloads: Added the ability to copy the block, shortcode, or add to cart links to the clipboard from the Download Edit screen. +* Downloads: Improved the registration and rendering of metaboxes. +* Downloads: Improved the variable pricing UI for the Download Edit screen. +* Downloads: Refreshed and combined Download metaboxes for improved download management. +* Emails: Fixed an issue that could result in the password reset email from the Auto Register extension not being sent. +* HTML: All HTML element classes now run `get_data_elements` to ensure that the data elements are available. +* HTML: Improved search results for Chosen select fields. +* HTML: Introduced the `Number` input field. +* i18n: Fixed an issue resulting in an undefined variable notice in translations. +* Logs: Updated the date column of the Logs to reflect the store timezone (instead of UTC) and improved the formatting. +* Orders: Corrected an issue resulting in the order total being incorrect when combining fees and taxes. +* Pages: Fixed an issue that resulted in 'noindex nofollow' tags being added when no checkout page is set. +* PayPal: Ensure that the merchant status check accounts for all exceptions. +* Reports: Added support for filtering by order status on the Payment Gateways reports. +* Reports: Current Period reports no longer show 'Hour by Hour' when less than two days of the period have passed. +* Reports: Improved the tooltip handling for reports to be more consistent. +* Reports: Separated many charts into individual charts to improve usability and flexibility. +* Reports: The Payment Gateways list table report now filters reports by the selected gateways, instead of linking to a list of orders for the gateway. +* Sessions: Prevent an undefined variable notice. +* Stripe: Added support for TWINT, and Revolut Pay. +* Stripe: Always get mandates for India-based customers/stores. +* Stripe: Fixed an issue that could cause a gateway error if the Payment Method Configuration cannot be retrieved. +* Stripe: Improved compatibility with Zero Decimal Currencies. +* Stripe: Improved performance by switching to using `Order` objects instead of `EDD_Payment` objects. + += 3.3.5.2 = +* Checkout: Fixed a user's first/last name not saving to their WordPress account when registering during checkout. +* Checkout: Fixed purchase data being sent to some gateways which process credit cards from form data. + += 3.3.5.1 = +* Stripe: Fixed issues with displaying and saving Stripe settings. + += 3.3.5 = +* NEW! Added Support for more Stripe Payment Methods, including more wallets, mobile payment methods, and some bank redirects. +* Stripe: Improved accessibility of the checkout form. +* Stripe: Improved compatibility with Stripe with customized checkouts missing form attributes. +* Stripe: Properly handle error messages when trying to create webhooks without HTTPS. +* Stripe: Updated the Stripe PHP library. +* PayPal: Fixed an issue preventing PayPal from fully connecting in test mode. +* PayPal: Improved the messaging around the PayPal settings screen. +* PayPal: Fixed an issue with the date_created on some PayPal Commerce orders. +* WP-REST: Improved integration with the WP-REST API media endpoint. +* Receipt: Improved hash validation when viewing a receipt as a guest. +* Emails: Fixed a possible fatal error with the WP Mail SMTP plugin. +* Emails: The Banned Email list filter is now accessible even when the list is empty. +* Checkout: Improved handling of guest customers. +* Checkout: Prevent an edge case where a warning could be thrown during checkout. +* HTTP: Introduced a standard HTTP Request utility to make consistent HTTP requests. +* Discounts: Prevent a JavaScript error when editing a discount. +* Administration: Vertical navigation elements now support the Back/Forward browser navigation. + += 3.3.4 = +* NEW! Stripe: When connecting EDD to Stripe, the necessary webhooks are registered. +* Cart: Enhanced cookie handling when emptying and re-adding items to cart. +* Checkout: Resolved undefined variable issue when using block registration form. +* Downloads: Improved response header handling to prevent invalid content-length headers. +* Downloads: Resolved inconsistency in author availability for new vs existing downloads. +* Emails: Improved email tag loading for more efficient processing. +* Emails: Removed image insertion capability from password reset email editor. +* Emails: Corrected filter name in new user email for proper functionality. +* Emails: Fixed a fatal error in password reset due to missing parameters. +* Emails: Fixed empty {password_link} email tag for first-time users. +* File System: Consolidate filesystem interactions by normalizing file paths. +* Orders: Improve order total calculations for orders with multiple adjustments. +* Orders: Fixed an issue with resumed orders and date_created values. +* Orders: Deleting an order now deletes the related transactions entry. +* Orders: Implemented safeguard to prevent negative order totals. +* Reports: Fixed PHP notices occurring in certain time zones. +* Sessions: Added new filters for more flexible cookie management. +* Sessions: Added built-in support for Pantheon cookie exclusions. +* Sessions: Added built-in support for WP Engine cookie exclusions. +* Stripe: Improve legacy card elements session compatibility. +* Users: Added a tool to help fix corrupted capabilities that prevent access to EDD settings screens. + += 3.3.3 = +* Reports: Refund tiles for fully refunded items could include partial refunds. +* Sessions: Improve session handling during checkout when logging in a user at the same time. +* Admin: Fixed a display issue when searching for EDD items in list views. +* Emails: Prevent a PHP error related to the Email Summary settings. +* Emails: Improved the Email Summaries reliability. +* Blocks: Fixed an issue on the Checkout block for existing customers without a last name. +* Downloads: Improved the handling of the cart widget markup when adding an item to the cart. +* Downloads: Improved the detection and handling of local files when they are downloaded. +* Settings: Refactored the process of saving settings for consistency. + += 3.3.2 = +* Checkout: Improved session handling with Stripe payments. +* Settings: Improved pass validation that could cause deactivation on certain server configurations. +* Admin: Improve responsiveness of EDD admin menus. +* Sessions: Set default session handling to database on new installs. +* i18n: Ensure that some block strings are able to be translated. + += 3.3.1 = +* Settings: Fixed a PHP warning related to a store's region when managing settings. +* Settings: Ensure that the selected currency option is a registered currency. +* Orders: Fixed an issue resulting in searching for orders not always working. +* Emails: Ensure that email subjects do not contain HTML tags. +* Emails: The Admin Sale Notification wasn't sending to multiple recipients. +* Checkout: Improved checkout page detection compatibility for extensions. +* Onboarding: Improved the onboarding wizard styles and content. + += 3.3.0 = +* Important: This release requires WordPress 6.0 or higher. +* Emails: **NEW** Managing emails is now easier than ever with the new Email Editor. All emails are now visible and managed from Downloads > Emails. +* Emails: **NEW** You can now easily toggle the status of most emails to enable or disable them. +* Emails: **NEW** Email tags have been improved to be more widely available to extensions and are now context aware by email type and recipient. +* Emails: **NEW** You can now send an email to the customer when an order is marked as refunded. +* Emails: **NEW** Added email tags for the refund amount and refund ID. +* Emails: **NEW** You can now see a log of emails sent to customers by EDD in Downloads > Emails > Logs. +* Emails: **NEW** Added a new email tag for the order fees. +* Emails: **NEW** Added the ability to email store owners about the Stripe "Early Fraud Warning" webhook. +* Emails: **NEW** You can now easily restore the default email templates from the editor. +* Emails: More emails are now able to be previewed and tested. +* Emails: Added the ability to edit the New User, Password Reset, and User Verification emails. +* Emails: Email settings have been moved to Downloads > Emails > Settings. +* Sessions: **NEW** The session handling has been rewritten from the ground up to be more reliable and improve performance. +* Sessions: The session storage method can now be changed via a setting in Downloads > Settings > Misc. +* Sessions: Added a custom database table for session storage, when using the database storage method. +* Sessions: Improved the performance and reliability of the session cleanup Cron event. +* Sessions: Improved session handling to only start when necessary, allowing more pages to be cached for guests. +* Extensions: The Disable Purchase Receipts extension is now marked as legacy and will be deactivated, as the functionality is now built into EDD. +* Extensions: The Auto Register extension is now marked as legacy and will be deactivated, as the functionality is now built into EDD. +* Stripe: Improved the handling when a Stripe customer cannot be found. +* Stripe: Added a disconnect button to the Stripe settings if the Stripe account that is connected has been closed. +* Stripe: Improved and implemented fallbacks to reduce failures when processing Stripe payments. +* PayPal: Improved the onboarding process for PayPal to avoid a possible fatal error. +* HTML: The Product Dropdown element has been improved for reliability, search, and performance. +* Migration: Improved the reliability of the EDD 3.0+ migration routine. +* Reports: When the percentage difference was calculated, it was possible for a positive change to show as a negative percentage. +* Cart: In some cases, the Add to Cart button was not redirecting logged-in users to the checkout page. +* Checkout: Improved the handling of the purchase form when showing the login form. +* Checkout: Improved the handling of the address fields when using the Stripe Payment Element. +* Checkout: Improved purchase validation during the checkout process. +* Exports: The Orders Export could sometimes use the product ID as the User ID. +* WPCron: We've refactored how EDD uses WPCron to improve performance and reliability. +* Customers: Improved the validation when removing email addresses from a customer. +* Admin: Many of our 'checkbox' settings have been updated to use the new 'toggle' setting type. +* Admin: Improved the consistency of the EDD Admin Pages. +* i18n: Updated the list of regions for the UK. +* i18n: Added Regions for Benin. +* i18n: We reviewed, updated, and improved a large number of translations with translator comments and context. +* Performance: Improved integration with the Heartbeat API to reduce the number of requests. +* Refunds: Improved the reliability and performance of generating a refund number. + += 3.2.12 = +* Compatibility: Fixed a conflict between Setup Wizard and the Multi Currency extension. +* Stripe: Improved the handling of refunding charges via the Stripe Dashboard. +* Exports: Improved performance, management, and protection of export files generated by EDD. +* Emails: Improved the validation before installing the Recapture integration. + += 3.2.11 = +* Order Management: When searching orders, a Product ID was being erroneously added to the filter arguments when it should not have been. + += 3.2.10 = +* Downloads: Improved performance when creating and managing download directories. +* Discounts: Start and End dates are properly handling the timezone. +* Discounts: Improved the accuracy of the check for if a store has active discounts. +* Discounts: A background upgrade process will be run to ensure all discounts have valid start and end dates. +* Dev: Debug log is now being managed within EDD's upload directory. +* Payments: Improved the detection and handling of purchase sessions when displaying the Receipt and Confirmation pages. +* Performance: Optimized the amount of data stored in the options table related to changelogs. +* HTML: The Product Dropdown could be forced to include items that were not products. +* HTML: The Product Dropdown has had its placeholder value improved. +* HTML: The Product Dropdown could have an unexpected empty option in the list of options. +* HTML: The Textarea HTML field could add empty content to the value. +* Checkout: The Privacy Policy checkbox may not always show when expected. +* Blocks: Improved loading of Easy Digital Downloads blocks. +* Blocks: Improved the handling of Easy Digital Downloads blocks when using a classic theme. +* Orders: Manually creating orders in the admin could fail to save the order due to a capability check. +* Migration: Improved the reliability of the EDD 3.0+ migration routine for possibly corrupt serialized data. +* Migration: Improved PHP 8.0+ compatibility for the EDD 3.0+ migration routine. + += 3.2.9 = +* Stripe: Resending the purchase receipt could fail if including the Stripe statement descriptor in the email template. +* Settings: Improved the reliability of determining the timezone settings in WordPress. +* Reports: When using custom dates for reporting, some timezone settings could make the dates show incorrectly. +* Emails: Adjusted when the deprecation notices will be shown for the legacy email hooks. + += 3.2.8 = +* Stripe: Resolved an issue that could cause card payments to fail when providing a Bank statement description with the `statement_descriptor` parameter. +* Stripe: IMPORTANT! The Statement Description settings in the Stripe gateway settings are no longer editable. The description is now automatically pulled from the Stripe account settings. +* Stripe: Buy Now buttons could throw a PHP deprecation notice. +* API: Added an option to enable logging public API queries (API Requests without a key & token) for the EDD API. +* Downloads: Improved the reliability of the download file access checks. +* Downloads: Searching downloads via the dropdown could return incorrect results. +* PayPal: Improved the reliability of the PayPal Merchant Account Connection process. +* Taxes: Improved the reliability of the tax calculations for orders with multiple items. +* Checkout: The Legend for checkout form elements could display incorrectly in Safari. +* HTML: Introduced a new Toggle Checkbox Class. +* Reports: A fatal error could occur if you supplied an invalid Download ID to the reports URL. +* System: Prevent a fatal error and improve the gathering of site system information. +* Compatibility: Improved checkout compatibility with Wordfence. + += 3.2.7 = +* Admin: Improved validation of saving some settings. +* API: Improved API Key management for admins, including showing the last time a key was used. +* Customers: Prevent updating a customer to have no email address. +* Downloads: Improved the consistency of the results of setting and getting a price option name. +* Downloads: Searching the downloads dropdown was not working correctly for international characters. +* Downloads: Improved download access validation. +* Fees: Improved the reliability of the fees and tax calculations. +* Orders: The Order Status Badge API now includes the order as a parameter on filters. +* Reports: Download Taxonomy reports have been improved for large data sets. +* Reports: Some timezones and date calculations were getting incorrect date ranges. +* Stripe: Improved mixed cart messaging. + += 3.2.6 = +* Checkout: Improved checkout block field handling for guest customers. +* Checkout: Login fields on checkout were incorrectly trying to process the checkout when hitting the 'Enter' key. +* Admin: Better compatibility when loading EDD footer and documentation links for non-store admin pages. +* Admin: Custom EDD admin pages could throw an error if incorrectly filtered. +* Downloads: Improved handling of detecting download variable prices. +* Downloads: Custom product dropdown could incorrectly exclude the download being edited. +* Reports: The store dashboard widget revenue and sales stats sometimes parsed dates with an incorrect offset. +* Reports: Pie charts for revenue currency values were not correctly applying the currency symbol. +* Reports: Improve Export Product Sales accuracy. +* Compatibility: Further improve PHP 8.0+ support. +* Shortcodes: Some shortcode attributes were hardened before being output. +* Orders: Better handling of refunding orders. +* Stripe: Better error messages when some cart contents are not compatible with the Stripe gateway. +* Discounts: Ensure store discounts recorded for orders are unique. + += 3.2.5 = +* Compatibility: Improved compatibility with servers not configured with mbstring. +* PayPal: Improved detection of PayPal order session handling and processing. + += 3.2.4 = +* Compatibility: Adds support for PHP 8.2. +* PayPal: Improve error handling of PayPal API responses. +* REST API: Download terms now have the featured images in the JSON response. +* Checkout: Improved the checkout block login and registration handling. +* Checkout: The legacy shortcode properly handles required fields. +* Emails: Improved sample data for the purchase confirmation preview. +* Refunds: Improved refund status handling and validation. +* Downloads: Bulk editing no longer clears prices. +* Downloads: Improved the ability to filter the ‘supports buy now’ for a download. +* Downloads: Reliability of the file download limits was improved. +* Store: Encourage store owners to set a country to help prepare for upcoming features. +* Privacy: Enhanced the integration with the WordPress Privacy tools. +* Reports: Improved chart sizing to prevent overlap and overflow. +* Dates: Added a polyfill for servers without `mbstring`. +* Support: Made links to support and documentation more accessible. +* Taxes: Corrected an issue with subscription updates when taxes are enabled. +* Registration: Fixed an issue with Recaptcha on the registration form. + += 3.2.3 = +* Blocks: The user registration forms and lost password feature have been updated to allow customization via hooks. +* Stripe: Refunded payments initiated within the Stripe Dashboard now create a refund order in EDD. +* Cart: Improved product validation when adding an item to the cart. +* Emails: Admin payment & order notifications have been updated to allow further customization. +* Block Editor: Improved compatibility with themes and page builders. +* Payments: Ensure that tax rates are accurately stored when using legacy payment creation processes. +* Customers: Improved the performance and accuracy of checking if a user has previously made purchases. + += 3.2.2 = +* PayPal: The IPN Backup for PayPal was fixed to prevent a fatal error. +* PayPal: Improved the Buy Now with PayPal payments to bypass Checkout only when Buy Now is fully supported. +* PayPal: Improved the reliability of the PayPal IPN listener. +* Stripe: Improved the Buy Now with Stripe payments to bypass Checkout only when Buy Now is fully supported by the product. +* Stripe: The State/Region ensures that it is updated when the Country field is changed. +* Emails: Further improved the reliability of payment emails and admin payment notifications. +* Customers: Migrating the customer data from EDD 2.x to EDD 3.x is now more reliable. +* Customers: Existing guest customers can now use the EDD Registration form to create a user. +* Customers: Improved the customer email address detection and reliability. +* Payments: Updated the customer recent payments list to use the correct date format. +* Blocks: Improved the reliability of checkout when EDD Blocks or Gutenberg are disabled. + += 3.2.1 = +* Stripe: We’ve improved the error handling and informational messaging related to the Stripe Payments integration. +* PayPal: When new features are added to the PayPal integration, a background process attempts to register new payment endpoints automatically. +* API: Accessing the file download logs via the EDD API now supports querying by both customer ID and customer email address. + += 3.2.0 = +* Important: This release requires PHP 7.4 or higher and WordPress 5.8 or higher. +* Important: Amazon Pay has been formally deprecated and is no longer available for new installs. Existing stores using Amazon Pay are encouraged to explore other options. +* New: PHP 8.1 is now fully supported. +* New: Discount codes can now be applied to specific price variations. +* New: A discount code can now be set to include or exclude specific download categories. +* New: Store owners can now archive discount codes, to make managing their promotions simpler. +* New: The discount list now has improved visibility for the status and usage of discounts. +* New: A new ‘Service’ product type was added to the Product Type dropdown, to allow for non-downloadable products. +* New: Disputed payments in PayPal and Stripe set the order to a new ‘On Hold’ status, and restrict access to downloads. +* New: When a payment is disputed with PayPal or Stripe, a link to the dispute is added to the view order details in the admin along with order notes. +* New: The date that the completed order actions were run is now stored as part of the order itself, not as metadata, and will be displayed more clearly on the order details screens. Existing meta will be migrated in the background; notifications will be updated to keep store owners advised of progress. +* Improvement: Purchase receipts are now sent using deferred actions, to improve the performance of the payment processing. +* Improvement: The bundle product metabox has been improved for performance on sites with many products. +* Improvement: The query for non-bundled products has been updated to be more performant, specifically when editing a download product. +* Improvement: Improved performance of checkout and customer lookups by conditionally running legacy hooks and filters that contained payment objects. +* Improvement: Site Health and telemetry now check the health of the REST API via a test endpoint. +* Improvement: The application fee for Stripe payments is removed for pass holders who have access to the Stripe Pro extension even if they do not have Stripe Pro installed. +* Improvement: Easy Digital Downloads’ block styles have been updated for non block themes to load only when the block is rendered. +* Improvement: The EDD Products block now allows products to be filtered by author. +* Fix: Stripe Payment Elements no longer supported a theme of ‘none’, and ‘stripe’ has been defaulted. +* Fix: Added hardening around viewing the adjustments list table. +* Fix: To avoid conflicts with other plugins, critical vendor libraries have been moved to the EDD namespace. +* Fix: Regional support for Stripe now checks the Stripe account country before checking the store country. +* Fix: PayPal Commerce orders where not always being completed when using the confirmation page. +* Fix: The Stripe Payment Element now uses the browser’s locale for localization. +* Fix: Using the Import tool could produce a fatal error. +* Fix: Discount codes resulting in a 100% discount could restrict the cart from allowing the user to complete the purchase. +* Fix: Saved carts were not correctly saving for some users. +* Fix: When saving a cart with Stripe as the active gateway, the message stating the cart was saved was removed prematurely. +* Fix: Recalculating a customer’s stats was not always being scheduled correctly. +* Fix: Malformed add to cart URLs could result in a PHP warning being thrown. +* Fix: Flat discount can result in an incorrect rounding total depending on the cart conditions. +* Fix: Viewing the order details of a migrated order that had its discount deleted looked incomplete. +* Fix: It was possible for sites with incompatible MySQL versions to not see the appropriate warnings. +* Fix: Attempting to activate a pass with an empty license key could appear to be stuck. +* Fix: The Stripe JavaScript has been improved to be more efficient, remove only error messages, and only run when the Stripe gateway is selected. +* Fix: Stripe could attempt to update a property that is not supported by the Stripe API. +* Fix: New PayPal Commerce subscriptions might not be correctly marked as complete. +* Fix: EDD Blocks placeholder styles could interfere with WordPress Core placeholders. +* Fix: Additional, not officially supported fee data is now stored as order adjustment metadata. +* Dev: Many filters and hooks that passed EDD_Payment objects now have a complementary hook or filter that uses EDD\Orders\Order objects. +* Dev: Easy Digital Downloads is no longer registering the edd_payment and edd_discount post types. +* Dev: Easy Digital Downloads – Core now holds the Stripe base code, and Stripe Pro 3.0.0 only contains pro features. +* Dev: Code for old, unsupported versions of PHP has been removed. +* Dev: A new edd_after_order_actions hook has been introduced to manage all events which should happen when an order is created. This uses the order object for improved performance and any usage of edd_after_payment_actions should be updated accordingly. +* i18n: 141 Additions, 199 Removals/Location Changes, 17 Differences (counts are approximate). == Upgrade Notice == +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. -= 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 +Easy Digital Downloads 3.0+ was a substantial change to the way that EDD stores data and builds reporting. If you've created any custom integrations for payment processors, file downloads, billing changes, subscriptions, or custom reporting you will want to ensure you test these changes on a staging copy of your site before running the upgrade. diff --git a/src/API/WP/Attachments.php b/src/API/WP/Attachments.php new file mode 100644 index 00000000000..05bf145c23e --- /dev/null +++ b/src/API/WP/Attachments.php @@ -0,0 +1,193 @@ + array( 'filter_attachment_query', 10, 2 ), + 'post_updated' => array( 'clear_cache', 10, 3 ), + ); + } + + /** + * Filters the attachment query. + * + * @since 3.3.5 + * + * @param array $args Query arguments. + * @param WP_REST_Request $request The REST request. + */ + public function filter_attachment_query( $args, $request ) { + if ( current_user_can( 'edit_products' ) ) { + return $args; + } + + $download_ids = $this->get_download_ids(); + $args['post_parent__not_in'] = isset( $args['post_parent__not_in'] ) ? array_merge( $args['post_parent__not_in'], $download_ids ) : $download_ids; + + $attachment_ids = $this->get_attachment_ids_by_guid(); + $args['post__not_in'] = isset( $args['post__not_in'] ) ? array_merge( $args['post__not_in'], $attachment_ids ) : $attachment_ids; + + $detached_file_ids = $this->get_detached_files_ids(); + $args['post__not_in'] = isset( $args['post__not_in'] ) ? array_merge( $args['post__not_in'], $detached_file_ids ) : $detached_file_ids; + + $args['post_parent__not_in'] = array_unique( $args['post_parent__not_in'] ); + $args['post__not_in'] = array_unique( $args['post__not_in'] ); + + return $args; + } + + /** + * Gets the download IDs. + * + * Collects and stores all downloads IDs. + * + * @since 3.3.5 + * + * @return array + */ + private function get_download_ids() { + $download_ids = wp_cache_get( 'edd_download_ids', self::CACHE_GROUP ); + + if ( false === $download_ids ) { + $download_ids = get_posts( + array( + 'post_type' => 'download', + 'fields' => 'ids', + ) + ); + + wp_cache_set( 'edd_download_ids', $download_ids, self::CACHE_GROUP, DAY_IN_SECONDS ); + } + + return $download_ids; + } + + /** + * Gets the attachment IDs by GUID. + * + * Collects all attachment Post IDs where the GUID contains the EDD upload base directory. + * + * @since 3.3.5 + * + * @return array + */ + private function get_attachment_ids_by_guid() { + $file_ids_in_path = wp_cache_get( 'edd_attachments_by_path', self::CACHE_GROUP ); + + if ( false === $file_ids_in_path ) { + global $wpdb; + + $edd_upload_dir = edd_get_upload_dir(); + $edd_upload_basedir = str_replace( ABSPATH, '', trailingslashit( $edd_upload_dir ) ); + + $file_ids_in_path = $wpdb->get_col( + $wpdb->prepare( + "SELECT ID FROM $wpdb->posts + WHERE post_type = 'attachment' + AND guid LIKE %s", + '%' . $wpdb->esc_like( $edd_upload_basedir ) . '%' + ) + ); + + wp_cache_set( 'edd_attachments_by_path', $file_ids_in_path, self::CACHE_GROUP, DAY_IN_SECONDS ); + } + + return $file_ids_in_path; + } + + /** + * Get attachment IDs that are not attached to a download or are not in our uploads directory. + * + * @since 3.3.5 + * + * @return array + */ + private function get_detached_files_ids() { + $detached_file_ids = wp_cache_get( 'edd_detached_file_ids', self::CACHE_GROUP ); + + if ( false === $detached_file_ids ) { + global $wpdb; + + $download_files = $wpdb->get_results( + "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = 'edd_download_files'" + ); + + $found_file_ids = array(); + + // Loop through the results and collect the file IDs. + foreach ( $download_files as $download_file ) { + $file_data = maybe_unserialize( $download_file->meta_value ); + + foreach ( $file_data as $file ) { + // If we got an attachment ID, add it to the list and move to the next item. + if ( ! empty( $file['attachment_id'] ) ) { + $found_file_ids[] = absint( $file['attachment_id'] ); + continue; + } + + $file_path = FileSystem::sanitize_file_path( $file['file'] ); + $file_id = $wpdb->get_var( + $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE guid = %s", $file_path ) + ); + + if ( $file_id ) { + $found_file_ids[] = absint( $file_id ); + } + } + } + + $detached_file_ids = array_unique( $found_file_ids ); + + wp_cache_set( 'edd_detached_file_ids', $detached_file_ids, self::CACHE_GROUP, DAY_IN_SECONDS ); + } + + return $detached_file_ids; + } + + /** + * Clears the cache when a download is updated. + * + * @since 3.3.5 + * @return void + */ + public function clear_cache( $post_id, $post, $update ) { + if ( 'download' === $post->post_type ) { + wp_cache_delete_multiple( + array( + 'edd_download_ids', + 'edd_attachments_by_path', + 'edd_detached_file_ids', + ), + self::CACHE_GROUP + ); + } + } +} diff --git a/src/Admin/Assets/Localization.php b/src/Admin/Assets/Localization.php new file mode 100644 index 00000000000..c23d16e9c8a --- /dev/null +++ b/src/Admin/Assets/Localization.php @@ -0,0 +1,176 @@ + esc_html__( 'Migration complete', 'easy-digital-downloads' ), + ) + ); + } + + /** + * Get the localization variables for the admin. + * + * @since 3.3.0 + * @return array + */ + private static function get_variables() { + + $edd_currency = new \EDD\Currency\Currency( self::get_currency() ); + + return 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: %s: Download singular label */ + 'delete_payment_download' => sprintf( __( 'Are you sure you want to delete this %s?', 'easy-digital-downloads' ), edd_get_label_singular() ), + /* translators: %s: Downloads plural label */ + 'type_to_search' => sprintf( __( 'Type to search %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + /* translators: %s: Download singular label */ + 'one_option' => sprintf( __( 'Choose a %s', 'easy-digital-downloads' ), edd_get_label_singular() ), + /* translators: %s: Downloads plural 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' ), + 'no_letters_or_numbers' => __( 'Either Letters or Numbers should be selected.', 'easy-digital-downloads' ), + 'delete_price_id' => __( 'Deleting a price ID with existing orders can cause issues with your store. Are you sure you want to delete this?', 'easy-digital-downloads' ), + + // Diaglog buttons. + 'confirm_dialog_text' => __( 'Confirm', 'easy-digital-downloads' ), + 'cancel_dialog_text' => __( 'Cancel', 'easy-digital-downloads' ), + 'copy_success' => __( 'Copied', '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' ), + 'download_has_files' => self::download_has_files(), + + // Region settings. + 'enter_region' => __( 'Enter a region', 'easy-digital-downloads' ), + 'select_region' => __( 'Select a region', 'easy-digital-downloads' ), + ); + } + + /** + * Gets the currency code. + * + * @since 3.3.0 + * @return string + */ + private static function get_currency() { + $currency = edd_get_currency(); + + // Customize the currency on a few individual pages. + if ( ! function_exists( 'edd_is_admin_page' ) ) { + return $currency; + } + + if ( edd_is_admin_page( 'reports' ) && function_exists( '\EDD\Reports\get_filter_value' ) ) { + /* + * 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() ) ) { + return 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 ) { + return $order->currency; + } + } + + return $currency; + } + + /** + * Check if the current download has files attached. + * + * @since 3.3.0 + * @return bool + */ + private static function download_has_files() { + if ( function_exists( 'edd_is_admin_page' ) && edd_is_admin_page( 'download', 'edit' ) ) { + return (bool) edd_get_download_files( get_the_ID() ); + } + + return false; + } +} diff --git a/src/Admin/Assets/Scripts.php b/src/Admin/Assets/Scripts.php new file mode 100644 index 00000000000..5b54b74f85f --- /dev/null +++ b/src/Admin/Assets/Scripts.php @@ -0,0 +1,149 @@ + $deps ) { + wp_register_script( + 'edd-admin-' . $page, + $js_dir . 'edd-admin-' . $page . '.js', + array_merge( $admin_deps, $deps ), + $version, + true, + ); + } + } + + /** + * Enqueue the EDD admin scripts. + * + * @since 3.3.0 + * @param string $hook The current admin page hook. + * @return void + */ + public static function enqueue( $hook = '' ) { + if ( ! edd_should_load_admin_scripts( $hook ) ) { + return; + } + + /** + * 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' ); + + // Enqueue media on EDD admin pages + wp_enqueue_media(); + + // 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', + ); + + // Loop through and enqueue the scripts + foreach ( $scripts as $script ) { + wp_enqueue_script( $script ); + } + + // Downloads page. + if ( edd_is_admin_page( 'download' ) ) { + wp_enqueue_script( 'edd-admin-downloads' ); + } + + if ( ( edd_is_admin_page( 'download', 'edit' ) || edd_is_admin_page( 'download', 'new' ) ) && get_current_screen()->is_block_editor() ) { + wp_enqueue_script( 'edd-admin-downloads-editor' ); + } + + // 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' ); + } + } + + /** + * Get the admin pages and their dependencies. + * + * @since 3.3.0 + * @return array + */ + private static function get_admin_pages() { + return array( + 'customers' => 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', + ), + 'emails-editor' => array( + 'wp-tinymce', + ), + 'emails-list-table' => array(), + // Backwards compatibility. + 'payments' => array(), + 'reports' => array( + 'edd-chart-js', + ), + 'settings' => array(), + 'tools' => array( + 'edd-admin-tools-export', + ), + 'upgrades' => array(), + ); + } +} diff --git a/src/Admin/Assets/Styles.php b/src/Admin/Assets/Styles.php new file mode 100644 index 00000000000..70d54b2b078 --- /dev/null +++ b/src/Admin/Assets/Styles.php @@ -0,0 +1,84 @@ + array( 'output_button', 10 ), + 'edd_add_discount_form_after_code_field_wrapper' => array( 'output_controls', 10 ), + ); + } + + /** + * Outputs the button to generate a discount code. + * + * @since 3.2.0 + */ + public function output_button() { + ?> + + + + + get_control_data(); + ?> +
    +
    +

    +
    +

    +

    +
    +
    +
    + __( 'Unlock with Pro', 'easy-digital-downloads' ), + 'message' => __( 'Upgrade to Easy Digital Downloads (Pro) to easily generate unique discount codes, and more.', 'easy-digital-downloads' ), + 'button_url' => edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_content' => 'upgrade-to-pro', + 'utm_medium' => 'discount-code-generator', + ), + false + ), + 'button_text' => __( 'Upgrade to Pro', 'easy-digital-downloads' ), + 'target' => '_blank', + ); + } +} diff --git a/src/Admin/Downloads/Editor/Details.php b/src/Admin/Downloads/Editor/Details.php new file mode 100644 index 00000000000..550688ab999 --- /dev/null +++ b/src/Admin/Downloads/Editor/Details.php @@ -0,0 +1,218 @@ +item; + if ( ! current_user_can( 'edit_product', $download->ID ) ) { + return; + } + + $types = edd_get_download_types(); + $type = edd_get_download_type( $download->ID ); + ksort( $types ); + ?> +
    + +
    + $types, + 'name' => '_edd_product_type', + 'id' => '_edd_product_type', + 'selected' => $type, + 'show_option_all' => false, + 'show_option_none' => false, + 'class' => 'edd-form-group__input edd-supports', + 'data' => array( + 'edd-supported' => 'product-type', + ), + ) + ); + $select->output(); + ?> +
    +
    + + get_price(); + $currency_position = edd_get_option( 'currency_position', 'before' ); + ?> + +
    + +
    + + + + + 'edd_price', + 'id' => 'edd_price', + 'value' => isset( $price ) ? esc_attr( edd_format_amount( $price ) ) : '', + 'class' => array( 'edd-amount-input', 'edd-price-field', 'no-controls', 'symbol-' . $currency_position ), + 'include_span' => false, + ) + ); + + $price_input->output(); + if ( 'after' === $currency_position ) { + ?> + + + + ID ); ?> +
    +
    + + ID ); ?> + + ID, $download ); + ?> + +
    + +
    + +
    +
    + + has_variable_prices() ); ?> /> + +
    +
    + +
    +
    + '_edd_price_options_mode', + 'current' => edd_single_price_option_mode( $download->ID ), + 'class' => 'edd-form-group__input', + 'label' => apply_filters( + 'edd_multi_option_purchase_text', + __( 'Allow purchasing multiple variations in the same order.', 'easy-digital-downloads' ) + ), + ) + ); + $toggle->output(); + ?> +
    +
    + + ID, $download ); + } +} diff --git a/src/Admin/Downloads/Editor/Files.php b/src/Admin/Downloads/Editor/Files.php new file mode 100644 index 00000000000..430bf26fad9 --- /dev/null +++ b/src/Admin/Downloads/Editor/Files.php @@ -0,0 +1,102 @@ + array( 'false', 'bundle' ), + ); + + /** + * Get the section label. + * + * @since 3.3.6 + * @return string + */ + public function get_label() { + return $this->item && 'bundle' === $this->item->type ? edd_get_label_plural() : __( 'Files', 'easy-digital-downloads' ); + } + + /** + * Render the section. + * + * @since 3.3.6 + * @return void + */ + public function render() { + ?> +
    + item->ID, '' ); + ?> +
    + item->ID ) ) { + $config['supports'] = null; + } + + return $config; + } +} diff --git a/src/Admin/Downloads/Editor/Notes.php b/src/Admin/Downloads/Editor/Notes.php new file mode 100644 index 00000000000..f24ea6b88c7 --- /dev/null +++ b/src/Admin/Downloads/Editor/Notes.php @@ -0,0 +1,91 @@ +item; + // Check if the user can edit this specific download ID (post ID). + if ( ! $download || ! current_user_can( 'edit_product', $download->ID ) ) { + return; + } + + $product_notes = edd_get_product_notes( $download->ID ); + ?> +
    +
    + + +
    +

    + +

    +
    + requires; + $config['supports'] = $this->supports; + + return $config; + } +} diff --git a/src/Admin/Downloads/Editor/Sections.php b/src/Admin/Downloads/Editor/Sections.php new file mode 100644 index 00000000000..6b461175f6d --- /dev/null +++ b/src/Admin/Downloads/Editor/Sections.php @@ -0,0 +1,233 @@ + + +
    +
    +
      + get_all_section_links(); ?> +
    + +
    + get_all_section_contents(); ?> +
    +
    + nonce_field(); + + if ( ! empty( $this->item ) ) : + ?> + + + + +
    + + sections as $section ) : + echo $this->get_section_link( $section ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + endforeach; + + // Return current buffer. + return ob_get_clean(); + } + + /** + * Gets a section link. + * + * @since 3.3.6 + * @param object $section The section to get a link for. + */ + public function get_section_link( $section, $doing_ajax = false ) { + ob_start(); + $id = $this->id . $section->id; + $classes = array( + 'section-title', + ); + if ( $this->is_current_section( $section->id ) ) { + $classes[] = 'section-title--is-active'; + } + ?> + + + is_current_section( $section->id ) + ? 'style="display: none;"' + : ''; + + $classes = $section->classes; + ob_start(); + ?> + +
    > + callback ) ) { + if ( is_callable( $section->callback ) ) { + call_user_func( $section->callback, $this->item ); + } elseif ( is_array( $section->callback ) && is_callable( $section->callback[0] ) ) { + $parameters = array(); + if ( isset( $section->callback[1] ) ) { + $parameters = $section->callback[1]; + } + call_user_func_array( $section->callback[0], $parameters ); + } else { + esc_html_e( 'Invalid section', 'easy-digital-downloads' ); + } + } else { + die; + do_action( 'edd_' . $section->id . 'section_contents', $this ); + } + + ?> +
    + sections ) ) { + return; + } + + // Loop through sections. + foreach ( $this->sections as $section ) : + echo $this->get_section_content( $section ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + endforeach; + } + + /** + * Is a section the current section? + * + * @since 3.3.6 + * @param string $section_id The section ID to check. + * @return bool + */ + protected function is_current_section( $section_id = '' ) { + return (bool) ( 'details' === $section_id ); + } + + /** + * Get the data attributes for a section. + * + * @param stdClass $section The section data. + * @return string + */ + private function get_section_data_attributes( $section ) { + $data_attributes = array(); + if ( ! empty( $section->requires ) ) { + if ( is_string( $section->requires ) ) { + $data_attributes[] = 'data-edd-requires-' . esc_attr( $section->requires ) . '="true"'; + } elseif ( is_array( $section->requires ) ) { + foreach ( $section->requires as $requirement => $value ) { + $data_attributes[] = 'data-edd-requires-' . esc_attr( $requirement ) . '="' . esc_attr( $value ) . '"'; + } + } + } + if ( ! empty( $section->supports ) ) { + foreach ( $section->supports as $key => $values ) { + $data_attributes[] = 'data-edd-supports-' . esc_attr( $key ) . '="' . implode( ',', array_map( 'esc_attr', $values ) ) . '"'; + } + } + + return ! empty( $data_attributes ) ? implode( ' ', $data_attributes ) : ''; + } +} diff --git a/src/Admin/Downloads/Editor/Settings.php b/src/Admin/Downloads/Editor/Settings.php new file mode 100644 index 00000000000..ab8e69354be --- /dev/null +++ b/src/Admin/Downloads/Editor/Settings.php @@ -0,0 +1,79 @@ +item; + // Check if the user can edit this specific download ID (post ID). + if ( ! $download || ! current_user_can( 'edit_product', $download->ID ) ) { + return; + } + + /** + * Output the settings fields. + * + * @since 1.9 + * @param int $download_id The post ID. + * @param \EDD_Download $download The download object. + */ + do_action( 'edd_meta_box_settings_fields', $download->ID, $this->item ); + } +} diff --git a/src/Admin/Downloads/Editor/VariablePrices.php b/src/Admin/Downloads/Editor/VariablePrices.php new file mode 100644 index 00000000000..28b50a66b9b --- /dev/null +++ b/src/Admin/Downloads/Editor/VariablePrices.php @@ -0,0 +1,351 @@ +item; + if ( ! $download ) { + $download = $item; + } + + $prices = $this->get_price_args( $download ); + + ?> +
    + +
    + $price ) { + $this->do_row( $download, $price, $key ); + } + ?> +
    + +
    + get_prices() : array(); + if ( ! empty( $prices ) ) { + return $prices; + } + + return array( + 1 => array( + 'index' => 1, + 'name' => '', + 'amount' => '', + ), + ); + } + + /** + * Render the row. + * This is essentially a replacement of `edd_render_price_row`. + * + * @since 3.3.6 + * + * @param \EDD_Download $download The download object. + * @param array $args The price args. + * @param int $price_id The key. + */ + public function do_row( $download, $args, $price_id ) { + ?> +
    + + +
    +
    #
    +
    + + 'edd_variable_prices[' . $price_id . '][name]', + 'id' => 'edd_variable_prices-' . $price_id . '-name', + 'value' => esc_attr( $args['name'] ), + 'placeholder' => __( 'Variation Name', 'easy-digital-downloads' ), + 'class' => array( 'edd_variable_prices_name', 'regular-text' ), + ) + ); + $text->output(); + ?> +
    + +
    + + + + + + 'edd_variable_prices[' . $price_id . '][amount]', + 'id' => 'edd_variable_prices-' . $price_id . '-amount', + 'value' => esc_attr( $args['amount'] ), + 'class' => array( 'edd-amount-input', 'edd-price-field', 'no-controls', 'symbol-' . $currency_position ), + 'placeholder' => edd_format_amount( 9.99 ), + 'include_span' => false, + ) + ); + + $price_input->output(); + if ( 'after' === $currency_position ) { + ?> + + + +
    + +
    + +
    +
    + + + + +
    + + do_remove_button( $download, $price_id ); ?> +
    + +
    + ID, $price_id, $args ); + } + ?> +
    + do_default_price_checkbox( $download, $price_id ); + $this->do_add_to_cart_button( $download, $price_id ); + ?> +
    +
    + + ID, $price_id ); + ?> +
    + get_default_price_id() ?: 1; + ?> +
    + /> + +
    + 'add_to_cart', + 'download_id' => (int) $download->ID, + 'edd_options[price_id]' => (int) $price_id, + ), + edd_get_checkout_uri() + ); + ?> + + + $download->ID, + 'price_id' => $price_id, + 'status__in' => edd_get_deliverable_order_item_statuses(), + 'number' => 1, + ) + ); + if ( ! empty( $has_orders ) ) { + $button_classes[] = 'edd-promo-notice__trigger'; + $button_classes[] = 'edd-promo-notice__trigger--ajax'; + } + ?> + + '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/Downloads/Metabox.php b/src/Admin/Downloads/Metabox.php new file mode 100644 index 00000000000..982e69066ed --- /dev/null +++ b/src/Admin/Downloads/Metabox.php @@ -0,0 +1,164 @@ + 'swap_download_type', + 'add_meta_boxes' => array( 'add_meta_boxes', 9, 2 ), + 'wp_ajax_edd_clone_variation' => 'clone_variation', + ); + } + + /** + * Potentially swaps out the download files HTML when the product type changes. + * + * @since 3.2.0 + */ + public function swap_download_type() { + if ( ! current_user_can( 'edit_products' ) || ! edd_doing_ajax() ) { + wp_send_json_error(); + } + + $download_id = ! empty( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null; + if ( is_null( $download_id ) || ! current_user_can( 'edit_product', $download_id ) ) { + wp_send_json_error(); + } + + ob_start(); + do_action( 'edd_meta_box_files_fields', $download_id, $this->get_download_type() ); + + wp_send_json_success( + array( + 'html' => ob_get_clean(), + 'label' => 'bundle' === $this->get_download_type() ? edd_get_label_plural() : __( 'Files', 'easy-digital-downloads' ), + ) + ); + } + + /** + * Adds the metaboxes. + * + * @since 3.3.6 + * @param string $post_type The post type. + * @param object $post The post object. + */ + public function add_meta_boxes( $post_type, $post ) { + + if ( ! $post instanceof \WP_Post ) { + return; + } + + $download = edd_get_download( $post->ID ); + + foreach ( $this->get_registered_metaboxes( $post->ID ) as $metabox ) { + if ( ! $metabox instanceof Metaboxes\Metabox ) { + continue; + } + + $metabox->set_download( $download ); + $config = $metabox->get_config(); + + add_meta_box( + $config['id'], + $config['title'], + $config['callback'], + $config['screen'], + $config['context'], + $config['priority'], + $config['callback_args'] + ); + } + } + + /** + * Clones a section via AJAX. + * + * @since 3.3.6 + */ + public function clone_variation() { + if ( ! current_user_can( 'edit_products' ) || ! edd_doing_ajax() ) { + wp_send_json_error(); + } + + $token = filter_input( INPUT_POST, 'token', FILTER_SANITIZE_SPECIAL_CHARS ); + $timestamp = filter_input( INPUT_POST, 'timestamp', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( ! \EDD\Utils\Tokenizer::is_token_valid( $token, $timestamp ) ) { + wp_send_json_error(); + } + $section_id = filter_input( INPUT_POST, 'section', FILTER_SANITIZE_NUMBER_INT ); + if ( empty( $section_id ) ) { + wp_send_json_error(); + } + $download_id = filter_input( INPUT_POST, 'download_id', FILTER_SANITIZE_NUMBER_INT ); + if ( empty( $download_id ) || ! current_user_can( 'edit_product', $download_id ) ) { + wp_send_json_error(); + } + + $section = new Editor\VariablePrices(); + + ob_start(); + $section->do_row( + edd_get_download( $download_id ), + array( + 'name' => '', + 'amount' => '', + 'index' => $section_id, + ), + $section_id + ); + + wp_send_json_success( ob_get_clean() ); + } + + /** + * Gets the download type. + * + * @since 3.2.3 + * @return string + */ + private function get_download_type() { + return ( ! empty( $_POST['product_type'] ) && 'bundle' === $_POST['product_type'] ) ? 'bundle' : 'default'; + } + + /** + * Gets the registered metaboxes. + * + * @since 3.3.6 + * @return array + */ + private function get_registered_metaboxes( $post_id ) { + $metaboxes = array( + new Metaboxes\Details(), + ); + if ( current_user_can( 'view_product_stats', $post_id ) ) { + $metaboxes[] = new Metaboxes\Stats(); + } + + return apply_filters( 'edd_download_editor_metaboxes', $metaboxes ); + } +} diff --git a/src/Admin/Downloads/Metaboxes/Details.php b/src/Admin/Downloads/Metaboxes/Details.php new file mode 100644 index 00000000000..9d798c3763c --- /dev/null +++ b/src/Admin/Downloads/Metaboxes/Details.php @@ -0,0 +1,144 @@ +ID; + if ( is_numeric( $post_id ) && ! current_user_can( 'edit_product', $post_id ) ) { + return; + } + + if ( is_null( $post_id ) && ! current_user_can( 'edit_products' ) ) { + return; + } + + if ( ! $this->download ) { + return; + } + + $registered_sections = $this->get_registered_sections( $this->download ); + if ( empty( $registered_sections ) ) { + return; + } + + $metabox_sections = new Editor\Sections(); + $metabox_sections->set_sections( $registered_sections ); + $metabox_sections->set_item( $this->download ); + $metabox_sections->display(); + + /** + * Output the download details metabox. + * + * @since 3.3.6 + * @param \EDD_Download $download The download. + * @param \WP_Post $post The post object. + */ + do_action( 'edd_metabox_download_details', $this->download, $post ); + + wp_nonce_field( 'edd_metabox_download_details', 'edd_download_meta_box_nonce' ); + } + + /** + * Get the sections. + * + * @since 3.3.6 + * @return array + */ + private function get_registered_sections( $download ) { + + /** + * Allow developers to add, remove, or modify the sections in the download details metabox. + * + * @since 3.3.6 + * @param array $sections Array of sections. + */ + $sections = apply_filters( + 'edd_download_details_sections', + array( + 'details' => Editor\Details::class, + 'variable-pricing' => Editor\VariablePrices::class, + 'files' => Editor\Files::class, + 'notes' => Editor\Notes::class, + 'settings' => Editor\Settings::class, + ) + ); + + if ( empty( $sections ) ) { + return array(); + } + + foreach ( $sections as $key => $section ) { + $valid_section = $this->validate_section( $section, $key ); + if ( ! $valid_section ) { + continue; + } + + $registered_sections[] = $valid_section->get_config(); + } + + // Now sort the sections by priority. + usort( $registered_sections, array( $this, 'sort_sections_by_priority' ) ); + + return $registered_sections; + } +} diff --git a/src/Admin/Downloads/Metaboxes/Metabox.php b/src/Admin/Downloads/Metaboxes/Metabox.php new file mode 100644 index 00000000000..26e871dedbc --- /dev/null +++ b/src/Admin/Downloads/Metaboxes/Metabox.php @@ -0,0 +1,169 @@ +download = $download; + } + + /** + * Gets the metabox configuration. + * + * @since 3.3.6 + * @return array + */ + public function get_config() { + return array( + 'id' => $this->id, + 'title' => $this->get_title(), + 'callback' => $this->get_callback(), + 'screen' => $this->get_screen(), + 'context' => $this->context, + 'priority' => $this->priority, + 'callback_args' => $this->get_callback_args(), + ); + } + + /** + * Gets the callback. + * + * @since 3.3.6 + * @return array + */ + protected function get_callback() { + return array( $this, 'render' ); + } + + /** + * Gets the screen. + * + * @since 3.3.6 + * @return array + */ + protected function get_screen() { + return apply_filters( 'edd_download_metabox_post_types', array( $this->screen ) ); + } + + /** + * Gets the additional arguments for the callback. + * + * @since 3.3.6 + * @return array + */ + protected function get_callback_args(): array { + return array(); + } + + /** + * Sort the sections by priority. + * + * @since 3.3.6 + * @param array $a The first section to compare. + * @param array $b The second section to compare. + * @return int + */ + protected function sort_sections_by_priority( $a, $b ) { + return $a['priority'] - $b['priority']; + } + + /** + * Validate the section. + * + * @since 3.3.6 + * @param string $section The section to validate. + * @return bool|EDD\Admin\Sections\Section + */ + protected function validate_section( $section, $id = '' ) { + if ( ! class_exists( $section ) ) { + return false; + } + + // Ensure that the section is a subclass of the base abstract class. + if ( ! is_subclass_of( $section, Section::class ) ) { + return false; + } + + // Return a new instance of the section. + $instantiated = new $section( $id ); + $instantiated->set_item( $this->download ); + + return $instantiated; + } +} diff --git a/src/Admin/Downloads/Metaboxes/Stats.php b/src/Admin/Downloads/Metaboxes/Stats.php new file mode 100644 index 00000000000..46de091515b --- /dev/null +++ b/src/Admin/Downloads/Metaboxes/Stats.php @@ -0,0 +1,130 @@ +ID ) ) { + return; + } + + $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 ), + ) + ); + ?> + + + + + +
    +

    + download The download object. + */ + do_action( 'edd_stats_meta_box', $this->download ); + } +} diff --git a/src/Admin/Emails/ListTable.php b/src/Admin/Emails/ListTable.php new file mode 100644 index 00000000000..8c6f8b10bbb --- /dev/null +++ b/src/Admin/Emails/ListTable.php @@ -0,0 +1,561 @@ +registry = edd_get_email_registry(); + + if ( function_exists( 'get_current_screen' ) ) { + $screen = get_current_screen(); + if ( $screen ) { + $screen->action = 'list'; + } + } + + parent::__construct( $args ); + } + + /** + * Gets a list of columns. + * + * The format is: + * - `'internal-name' => 'Title'` + * + * @since 3.3.0 + * + * @return array + */ + public function get_columns() { + return array( + 'name' => __( 'Email', 'easy-digital-downloads' ), + 'sender' => __( 'Sender', 'easy-digital-downloads' ), + 'context' => __( 'Context', 'easy-digital-downloads' ), + 'recipient' => __( 'Recipient', 'easy-digital-downloads' ), + 'subject' => __( 'Subject', 'easy-digital-downloads' ), + 'date_modified' => __( 'Updated', 'easy-digital-downloads' ), + 'status' => __( 'Status', 'easy-digital-downloads' ), + ); + } + + /** + * ID of the primary column. + * + * @since 3.3.0 + * + * @return string + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * Renders most columns. + * + * @since 3.3.0 + * @param \EDD\Emails\Templates\EmailTemplate $item The current item. + * @param string $column_name The column name. + * + * @return string + */ + protected function column_default( $item, $column_name ) { + $value = ''; + + switch ( $column_name ) { + case 'recipient': + $value = $this->get_value_from_array( $item->recipient, $this->registry->get_recipients() ); + break; + case 'context': + $value = $this->get_value_from_array( $item->get_context_label(), $this->registry->get_contexts() ); + break; + case 'subject': + $value = $item->subject; + break; + case 'sender': + $value = $this->get_value_from_array( $item->sender, $this->registry->get_senders() ); + break; + case 'date_modified': + $value = edd_date_i18n( strtotime( $item->date_modified ), get_option( 'date_format' ) ); + $value .= '
    '; + $value .= edd_date_i18n( strtotime( $item->date_modified ), get_option( 'time_format' ) ); + break; + default: + break; + } + + return $value; + } + + /** + * Renders the "Status" column. + * + * @since 3.3.0 + * @param \EDD\Emails\Templates\EmailTemplate $item The current item. + * @return string + */ + protected function column_status( $item ) { + $status = 'active'; + $label = __( 'Disable Email', 'easy-digital-downloads' ); + $action = 'disable'; + if ( ! $item->status ) { + $status = 'inactive'; + $label = __( 'Enable Email', 'easy-digital-downloads' ); + $action = 'enable'; + } + ob_start(); + $status_tooltip = $item->get_status_tooltip(); + if ( ! empty( $status_tooltip ) ) { + $tooltip = new Tooltip( $status_tooltip ); + $tooltip->output(); + } + ?> + + %s%s
    ', + esc_url( $item->get_edit_url() ), + $item->get_name(), + $this->maybe_add_extra_email_data( $item ) + ); + + return $name . $this->row_actions( $this->get_row_actions( $item ) ); + } + + /** + * Generates the tbody element for the list table. + * + * We're modifying the core method here, as we shouldn't ever have no-emails showing, however someone could apply filters to the list + * in a way that would result in no matching emails, so we need a hidden 'no items' row to display when no items are left in the list. + * + * @since 3.3.0 + */ + public function display_rows_or_placeholder() { + if ( $this->has_items() ) { + $this->display_rows(); + } + + // Add our hidden row for when no items are found. + echo ''; + $this->no_items(); + echo ''; + } + + /** + * Renders the "No items found" message. + * + * @since 3.3.0 + * @return void + */ + public function no_items() { + esc_html_e( 'No emails found matching filters.', 'easy-digital-downloads' ); + } + + /** + * Generates content for a single row of the table + * + * @since 3.3.0 + * @param \EDD\Emails\Templates\EmailTemplate $item The current item. + */ + public function single_row( $item ) { + + // Add custom data attributes based on filter options. + $attributes = array( + 'data-type="item"', + 'data-status="' . absint( $item->status ? 1 : 0 ) . '"', + 'data-recipient="' . esc_attr( $item->recipient ) . '"', + 'data-sender="' . esc_attr( $item->sender ) . '"', + 'data-context="' . esc_attr( $item->context ) . '"', + ); + + echo ''; + $this->single_row_columns( $item ); + echo ''; + } + + /** + * Retrieves the table classes for the Emails ListTable. + * We need to ensure our table does not have the "striped" class. + * + * @since 3.3.0 + * @return array An array of table classes. + */ + protected function get_table_classes() { + return array( 'widefat', 'fixed', 'table-view-list', $this->_args['plural'] ); + } + + /** + * Displays available filters. + * + * @since 3.3.0 + * @param string $which The position. + * @return void + */ + protected function display_tablenav( $which ) { + if ( 'top' !== $which ) { + ?> +
    + +
    + +
    +
    + do_status_filter(); + $this->do_sender_filter(); + $this->do_context_filter(); + $this->do_recipient_filter(); + ?> + +
    +
    + search_box( __( 'Search', 'easy-digital-downloads' ), 'emails' ); ?> + + +
    + registry->get_add_new_actions(); + if ( ! empty( $add_new_actions ) ) : + ?> +
    + do_new_actions_overlay( $add_new_actions ); ?> +
    + +
    + + + + _column_headers = array( + $this->get_columns(), + array(), + $this->get_sortable_columns(), + ); + + $this->items = array(); + $emails = $this->registry->get_emails(); + + $search = filter_input( INPUT_GET, 's', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( ! empty( $search ) ) { + $search_results = edd_get_emails( + array( + 'search' => $search, + 'fields' => 'email_id', + ) + ); + + foreach ( $emails as $key => $email_class_name ) { + if ( ! in_array( $key, $search_results, true ) ) { + unset( $emails[ $key ] ); + } + } + } + + // The key is important as it is used to manage dynamic emails. + foreach ( $emails as $key => $email_class_name ) { + try { + $email = $this->registry->get_email( $email_class_name, array( $key ) ); + if ( ! $email->can_view ) { + continue; + } + $this->items[] = $email; + } catch ( \Exception $e ) { + // Do nothing. + } + } + } + + /** + * Gets the row actions for an item. + * + * @since 3.3.0 + * @param \EDD\Emails\Templates\EmailTemplate $item The email template. + * @return array + */ + private function get_row_actions( $item ) { + $row_actions = array(); + $actions = $item->get_row_actions(); + foreach ( $actions as $action => $data ) { + $row_actions[ $action ] = sprintf( + '%s', + esc_url( $data['url'] ), + isset( $data['target'] ) ? ' target="' . esc_attr( $data['target'] ) . '"' : '', + esc_html( $data['text'] ) + ); + } + + return $row_actions; + } + + /** + * Renders the status filter. + * + * @since 3.3.0 + * @return void + */ + private function do_status_filter() { + ?> + + + + + + + + + + + + 'dashicons-info-outline', + 'content' => implode( '
    ', $extra_content ), + ) + ); + return $tooltip->get(); + } + + /** + * Retrieves the CSS class for a table row. This allows the table to mimic the WordPress + * Core "striped" table output, but we have to do it manually. + * + * @since 3.3.0 + * @return string The CSS class for the table row. + */ + private function get_row_class() { + static $row_class = ''; + $row_class = empty( $row_class ) ? 'alternate' : ''; + $class = 'edd-list-table__item'; + + return empty( $row_class ) ? $class : $class . ' ' . $row_class; + } + + /** + * Gets the value from an array. + * + * @since 3.3.0 + * @param string $value The selected value. + * @param array $options The array of options. + * @return string + */ + private function get_value_from_array( $value, $options ) { + return array_key_exists( $value, $options ) ? $options[ $value ] : $value; + } +} diff --git a/src/Admin/Emails/LogsTable.php b/src/Admin/Emails/LogsTable.php new file mode 100644 index 00000000000..caf6c841f3e --- /dev/null +++ b/src/Admin/Emails/LogsTable.php @@ -0,0 +1,277 @@ + 'email_log', + 'plural' => 'email_logs', + 'ajax' => false, + ) + ); + + $this->registry = new Registry(); + } + + /** + * Retrieve the table columns + * + * @since 3.3.0 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'subject' => __( 'Subject', 'easy-digital-downloads' ), + 'email' => __( 'To', 'easy-digital-downloads' ), + 'object_id' => __( 'Email Object', 'easy-digital-downloads' ), + 'date_created' => __( 'Date Sent', 'easy-digital-downloads' ), + ); + } + + /** + * Gets the name of the primary column. + * + * @since 3.3.0 + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'email'; + } + + /** + * This function renders the columns in the list table. + * + * @since 3.3.0 + * @param \EDD\Emails\LogEmail $item The current item. + * @param string $column_name The name of the column. + * @return string + */ + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + case 'date_created': + $date = edd_date_i18n( strtotime( $item->{$column_name} ), get_option( 'date_format' ) ); + $date .= '
    ' . edd_date_i18n( strtotime( $item->{$column_name} ), get_option( 'time_format' ) ); + + return $date; + + case 'object_id': + return $this->get_object_column( $item ); + + default: + return $item->{$column_name}; + } + } + + /** + * Gets the log entries for the current view + * + * @since 3.3.0 + * @param array $query The array of query vars. + * @return array Array of all the log entries. + */ + public function get_logs( $query = array() ) { + $logs = new LogEmail(); + + return $logs->query( $query ); + } + + /** + * Get the total number of items. + * + * @since 3.3.0 + * @param array $query The array of query vars. + * @return int + */ + public function get_total( $query = array() ) { + $logs = new LogEmail(); + $query = wp_parse_args( + $query, + array( + 'count' => true, + ) + ); + + return $logs->query( $query ); + } + + /** + * Get the query args. + * + * @since 3.3.7 + * @param bool $paginate Whether to paginate the query. + * @return array + */ + protected function get_query_args( $paginate = true ) { + return array( 'search' => $this->get_search() ); + } + + /** + * Display the tablenav. + * + * @since 3.3.7 + * @param string $which The display position. + */ + protected function display_tablenav( $which ) { + + ob_start(); + + if ( 'top' === $which ) { + $this->search_box( __( 'Search Logs', 'easy-digital-downloads' ), 'edd-email-logs' ); + } + + parent::display_tablenav( $which ); + + $output = ob_get_clean(); + + if ( 'top' === $which ) { + $output = sprintf( + '
    %s
    ', + $output + ); + } + + echo $output; + } + + /** + * Get the object column. + * + * @since 3.3.0 + * @param \EDD\Emails\LogEmail $item The current item. + * @return string + */ + private function get_object_column( $item ) { + $link = false; + + switch ( $item->object_type ) { + case 'order': + $order = edd_get_order( $item->object_id ); + + if ( $order ) { + $link = array( + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-payment-history', + 'view' => 'view-order-details', + 'id' => absint( $item->object_id ), + ) + ), + 'label' => sprintf( + /* translators: %s: Order number */ + __( 'Order %s', 'easy-digital-downloads' ), + $order->get_number() + ), + ); + } + break; + + case 'user': + $user = get_userdata( $item->object_id ); + + if ( $user ) { + $link = array( + 'url' => add_query_arg( + array( + 'user_id' => absint( $item->object_id ), + ), + admin_url( 'user-edit.php' ) + ), + 'label' => sprintf( + /* translators: %s: User display name */ + __( 'User %s', 'easy-digital-downloads' ), + $user->display_name + ), + ); + } + break; + + case 'refund': + $refund = edd_get_order( $item->object_id ); + + if ( $refund ) { + $link = array( + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-payment-history', + 'view' => 'view-refund-details', + 'id' => absint( $item->object_id ), + ) + ), + 'label' => sprintf( + /* translators: %s: Refund number */ + __( 'Refund %s', 'easy-digital-downloads' ), + $refund->get_number() + ), + ); + } + break; + + default: + break; + } + + if ( $link ) { + return sprintf( + '%2$s', + esc_url( $link['url'] ), + esc_html( $link['label'] ) + ); + } + + /** + * Allow extensions to filter the object column. + * + * @since 3.3.0 + * @param $item->object_id The object ID. + * @param $item The current item. + */ + return apply_filters( 'edd_emails_logs_table_object', $item->object_id, $item ); + } +} diff --git a/src/Admin/Emails/Manager.php b/src/Admin/Emails/Manager.php new file mode 100644 index 00000000000..240c0edb343 --- /dev/null +++ b/src/Admin/Emails/Manager.php @@ -0,0 +1,429 @@ + 'save', + 'wp_ajax_edd_update_email_status' => 'update_status', + 'edd_flyout_docs_link' => 'update_docs_link', + 'edd_email_editor_top' => 'description', + 'wp_ajax_edd_reset_email' => 'reset', + 'edd_render_settings_emails_sections' => 'remove_sections', + ); + } + + /** + * Saves the email settings. + * + * @since 3.3.0 + * @param array $data Data. + * @throws Exception If the email cannot be saved due to permissions, verification, or missing data. + */ + public function save( $data ) { + try { + if ( empty( $data['edd_save_email_nonce'] ) || ! wp_verify_nonce( $data['edd_save_email_nonce'], 'edd_save_email' ) ) { + throw new Exception( __( 'Nonce verification failed.', 'easy-digital-downloads' ) ); + } + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + throw new Exception( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ); + } + + if ( empty( $data['email_id'] ) ) { + throw new Exception( __( 'Missing email ID.', 'easy-digital-downloads' ) ); + } + + $email_saved = $this->save_email( $data ); + $message = empty( $email_saved['success'] ) ? 'email-not-saved' : 'email-saved'; + if ( ! empty( $email_saved['message'] ) ) { + $message = $email_saved['message']; + } + + edd_redirect( + edd_get_admin_url( + array( + 'page' => 'edd-emails', + 'edd-message' => $message, + 'email' => $email_saved['email_id'], + ) + ) + ); + } catch ( Exception $e ) { + wp_die( $e->getMessage() ); + } + } + + /** + * Updates the status of an email via ajax. + * + * @since 3.3.0 + */ + public function update_status() { + + $email_id = filter_input( INPUT_POST, 'email_id', FILTER_SANITIZE_SPECIAL_CHARS ); + // Check for the email ID. + if ( empty( $email_id ) ) { + wp_send_json_error( array( 'message' => __( 'Missing email ID.', 'easy-digital-downloads' ) ) ); + } + + // Validate the nonce. + if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'edd_update_email' ) ) { + wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'easy-digital-downloads' ) ) ); + } + + // Check for permissions. + if ( ! current_user_can( 'manage_shop_settings' ) ) { + wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ) ); + } + + // Get the email. + try { + $registry = edd_get_email_registry(); + $email = $registry->get_email_by_id( $email_id ); + } catch ( Exception $e ) { + wp_send_json_error( array( 'message' => $e->getMessage() ) ); + } + + if ( ! $email->can_edit( 'status' ) ) { + wp_send_json_error( array( 'message' => __( 'This email status cannot be changed.', 'easy-digital-downloads' ) ) ); + } + + $success = $this->set_status( $email ); + if ( $success ) { + wp_send_json_success( array( 'success' => $success ) ); + } + + wp_send_json_error( array( 'message' => __( 'This email status could not be changed.', 'easy-digital-downloads' ) ) ); + } + + /** + * Updates the docs link for the flyout menu. + * + * @since 3.3.0 + * @param string $link The link. + * @return string + */ + public function update_docs_link( $link ) { + if ( 'edd-emails' === filter_input( INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS ) ) { + $link = 'https://easydigitaldownloads.com/docs/emails/'; + + if ( 'email_summaries' === filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_SPECIAL_CHARS ) ) { + $link .= '#summaries'; + } + } + + return $link; + } + + /** + * Outputs the email description for the editor. + * + * @since 3.3.0 + * @param \EDD\Emails\Email $email The email. + */ + public function description( $email ) { + ?> +
    + get_description(); + $required_tag = $email->required_tag; + if ( ! empty( $required_tag ) ) { + $description .= '
    '; + $description .= sprintf( + /* translators: 1: opening strong tag, 2: closing string tag, 3: required tag */ + __( '%1$sImportant:%2$s The %3$s template tag must remain in this email. Do not delete it.', 'easy-digital-downloads' ), + '', + '', + '{' . $required_tag . '}' + ); + EDD()->email_tags->remove( $required_tag ); + } + echo wpautop( wp_kses_post( $description ) ); + ?> +
    + 0 ); + + return $email_id; + } + + /** + * Resets the email content via ajax. + * + * @since 3.3.0 + */ + public function reset() { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ) ); + } + $nonce = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'edd_update_email' ) ) { + wp_send_json_error( array( 'message' => __( 'Nonce verification failed.', 'easy-digital-downloads' ) ) ); + } + $email_id = filter_input( INPUT_POST, 'email_id', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( empty( $email_id ) ) { + wp_send_json_error( array( 'message' => __( 'Missing email ID.', 'easy-digital-downloads' ) ) ); + } + $email_template = edd_get_email_registry()->get_email_by_id( $email_id ); + if ( ! $email_template ) { + wp_send_json_error( array( 'message' => __( 'Invalid email ID.', 'easy-digital-downloads' ) ) ); + } + + wp_send_json_success( array( 'content' => wpautop( $email_template->get_default( 'content' ) ) ) ); + } + + /** + * Sets the status of the email. + * + * @since 3.3.0 + * @param \EDD\Emails\Templates\EmailTemplate $email_template The email template. + * @return bool + */ + private function set_status( $email_template ) { + $action = filter_input( INPUT_POST, 'button', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( ! empty( $email_template->email->id ) ) { + return edd_update_email( + $email_template->email->id, + array( + 'status' => 'enable' === $action, + ) + ); + } + + return $email_template->__set( 'status', 'enable' === $action ); + } + + /** + * Saves the email. + * This method runs after the user capabilities and nonce have been checked. + * Extensions can short-circuit this method by returning a value from the edd_email_templates_save_email filter. + * + * @since 3.3.0 + * @param array $data The email form data. + * @return array The result of the save. + */ + private function save_email( $data ) { + $email_id = $data['email_id']; + $registry = edd_get_email_registry(); + $email_template = $registry->get_email_by_id( $email_id ); + + if ( ! $email_template ) { + return array( + 'success' => false, + 'email_id' => $email_id, + ); + } + + /** + * Allow plugins to save the email using their own logic. + * If this filter returns anything other than false, the email will not be saved with the default logic. + * + * @since 3.3.0 + * @param string $email_id The email ID. + * @param \EDD\Emails\Templates\EmailTemplate $email_template The email being saved. + * @param array $data The data being saved. + */ + $email_id = apply_filters( 'edd_email_manager_save_email_id', $email_id, $email_template, $data ); + if ( empty( $email_id ) ) { + return array( + 'success' => false, + 'email_id' => $email_id, + ); + } + + $updated_data = array(); + $email = $email_template->get_email(); + foreach ( $this->get_filtered_form_data( $data ) as $key => $value ) { + if ( ! $email_template->can_edit( $key ) && ! array_key_exists( $key, $email_template->meta ) ) { + if ( property_exists( $email, $key ) && ! is_null( $email->{$key} ) ) { + $updated_data[ $key ] = $email->{$key}; + } elseif ( property_exists( $email_template, $key ) && ! is_null( $email_template->{$key} ) ) { + $updated_data[ $key ] = $email_template->{$key}; + } + continue; + } + + $updated_data[ $key ] = $this->sanitize( $value, $key ); + } + + $success = false; + if ( empty( $updated_data ) ) { + return array( + 'success' => $success, + 'email_id' => $email_id, + ); + } + + $required_tag = $email_template->required_tag; + if ( + ! empty( $required_tag ) && + ! empty( $updated_data['content'] ) && + false === strpos( $updated_data['content'], "{{$required_tag}}" ) + ) { + return array( + 'success' => false, + 'email_id' => $email_id, + 'message' => 'required-content-missing', + ); + } + + $updated_data['email_id'] = $email_id; + + if ( ! empty( $email->id ) ) { + $success = edd_update_email( $email->id, $updated_data ); + } else { + $id = edd_add_email( $updated_data ); + if ( $id ) { + $email = edd_get_email_by( 'id', $id ); + $email_id = $email->email_id; + $success = true; + } + } + $this->update_recipients( $email->id, $data ); + + return array( + 'success' => $success, + 'email_id' => $email_id, + ); + } + + /** + * Gets the filtered form data. + * + * @since 3.3.0 + * @param array $data The form data. + * @return array + */ + private function get_filtered_form_data( $data ) { + $skipped_fields = array( 'email_id', 'edd-action', 'edd_save_email_nonce', '_wp_http_referer', 'submit' ); + + return array_diff_key( $data, array_flip( $skipped_fields ) ); + } + + /** + * Sanitizes the value. + * + * @since 3.3.0 + * @param mixed $value The value. + * @param string $key The key. + * @return mixed + */ + private function sanitize( $value, $key ) { + if ( 'status' === $key ) { + return (int) (bool) $value; + } + + if ( in_array( $key, array( 'heading', 'subject' ), true ) ) { + return sanitize_text_field( $value ); + } + + return is_array( $value ) ? array_map( 'wp_kses_post', $value ) : wp_kses_post( $value ); + } + + /** + * Updates the recipients for admin emails. + * This method runs after the email has been saved. + * + * @since 3.3.0 + * @param int $email_id The email ID. + * @param array $data The data. + */ + private function update_recipients( $email_id, $data ) { + if ( empty( $data['admin_recipient'] ) ) { + edd_delete_email_meta( $email_id, 'recipients' ); + return; + } + + if ( 'default' === $data['admin_recipient'] ) { + edd_update_email_meta( $email_id, 'recipients', 'admin' ); + return; + } + + if ( 'custom' === $data['admin_recipient'] ) { + $recipients = $this->sanitize_recipients( $data['recipients'] ); + if ( ! empty( $recipients ) ) { + edd_update_email_meta( $email_id, 'recipients', $recipients ); + return; + } + } + + edd_delete_email_meta( $email_id, 'recipients' ); + } + + /** + * Sanitizes the recipients. + * + * @since 3.3.0 + * @param string $recipients The recipients. + * @return string + */ + private function sanitize_recipients( $recipients ) { + $recipients = sanitize_textarea_field( $recipients ); + $recipients = explode( "\n", $recipients ); + foreach ( $recipients as $key => $recipient ) { + $recipients[ $key ] = sanitize_email( $recipient ); + } + $recipients = array_filter( $recipients ); + + return implode( "\n", $recipients ); + } + + /** + * Removes the email summaries section from being displayed in the EDD Settings > Emails tab. + * + * @since 3.3.0 + * @param array $sections The sections. + * @return array + */ + public function remove_sections( $sections ) { + unset( $sections['email_summaries'] ); + + return $sections; + } +} diff --git a/src/Admin/Emails/Messages.php b/src/Admin/Emails/Messages.php new file mode 100644 index 00000000000..5d251883a54 --- /dev/null +++ b/src/Admin/Emails/Messages.php @@ -0,0 +1,116 @@ + array( 'notices', 5 ), + 'admin_notices' => 'notices', + ); + } + + /** + * Admin notices. + * + * @since 3.3.0 + * @return void + */ + public function notices() { + if ( ! edd_is_admin_page( 'emails' ) ) { + return; + } + if ( ! empty( $_GET['email'] ) && 'admin_notices' === current_action() ) { + return; + } + $notice = $this->get_notice(); + if ( ! $notice ) { + return; + } + + $args = wp_parse_args( + $notice, + array( + 'id' => 'edd-email-notice', + 'message' => '', + 'class' => 'updated', + ) + ); + if ( empty( $args['message'] ) ) { + return; + } + + ?> +
    +

    +
    + array( + 'id' => 'edd-email-required-content-missing', + 'message' => __( 'Your email could not be saved because it is missing required content.', 'easy-digital-downloads' ), + 'class' => 'error', + ), + 'test-email-sent' => array( + 'id' => 'edd-test-email-sent', + 'message' => __( 'The test email was sent successfully.', 'easy-digital-downloads' ), + ), + 'test-email-failed' => array( + 'id' => 'edd-test-email-failed', + 'message' => __( 'The test email could not be sent. Please check your email settings and try again.', 'easy-digital-downloads' ), + 'class' => 'error', + ), + 'email-added' => array( + 'id' => 'email-added', + 'message' => __( 'The email was successfully added.', 'easy-digital-downloads' ), + ), + 'email-add-failed' => array( + 'id' => 'email-add-failed', + 'message' => __( 'The email could not be added.', 'easy-digital-downloads' ), + 'class' => 'error', + ), + 'email-deleted' => array( + 'id' => 'email-deleted', + 'message' => __( 'The email was successfully deleted.', 'easy-digital-downloads' ), + ), + 'email-delete-failed' => array( + 'id' => 'email-delete-failed', + 'message' => __( 'The email could not be deleted.', 'easy-digital-downloads' ), + 'class' => 'error', + ), + ); + + return isset( $messages[ $message ] ) ? $messages[ $message ] : false; + } +} diff --git a/src/Admin/Emails/Reset.php b/src/Admin/Emails/Reset.php new file mode 100644 index 00000000000..75b3a50c1f3 --- /dev/null +++ b/src/Admin/Emails/Reset.php @@ -0,0 +1,83 @@ + +

    +

    +
    + + +
    + __( 'Emails', 'easy-digital-downloads' ), + 'settings' => __( 'Settings', 'easy-digital-downloads' ), + 'email_summaries' => __( 'Email Reports', 'easy-digital-downloads' ), + 'logs' => __( 'Logs', 'easy-digital-downloads' ), + ), + 'edd-emails' + ); + $navigation->render(); + ?> + +
    +
    + tabs ) ) { + if ( 'logs' === $current_tab ) { + self::render_logs(); + return; + } + + self::render_settings(); + return; + } + + self::render_table(); + ?> +
    + admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'edd_update_email' ), + 'debug' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, + 'link' => edd_get_admin_url( array( 'page' => 'edd-emails' ) ), + ) + ); + } + + /** + * Renders the email editor. + * + * @since 3.3.0 + * @throws Exception + */ + private static function render_email_editor() { + try { + if ( empty( $_GET['email'] ) ) { + throw new Exception( __( 'Missing email ID.', 'easy-digital-downloads' ) ); + } + + $registry = edd_get_email_registry(); + $email = $registry->get_email_by_id( sanitize_text_field( $_GET['email'] ) ); + + if ( ! $email ) { + throw new Exception( __( 'Invalid email ID.', 'easy-digital-downloads' ) ); + } + } catch ( Exception $e ) { + wp_die( $e->getMessage() ); + } + + require_once EDD_PLUGIN_DIR . 'includes/admin/views/email-editor/editor.php'; + } + + /** + * Renders the email table. + * + * @since 3.3.0 + */ + private static function render_table() { + $table = new ListTable( + array( + 'singular' => 'email_template', + 'plural' => 'email_templates', + 'ajax' => false, + ) + ); + $table->prepare_items(); + $table->display(); + } + + /** + * Renders the email settings. + * + * @since 3.3.0 + * @return void + */ + private static function render_settings() { + wp_enqueue_script( 'edd-admin-settings' ); + $tab = self::get_current_tab(); + if ( 'settings' === $tab ) { + $tab = 'main'; + } + ?> + +
    +
    + + + +
    +
    + prepare_items(); + ?> +
    + display(); + ?> + + + +
    + 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']; + + $style = ! empty( $this->product->style ) ? $this->product->style : false; + if ( 'overlay' === $style ) { + $this->do_card_overlay(); + } elseif ( 'installer' === $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(); + ?> +
    +
    + +
    +
    + do_icon(); + echo '
    '; + $this->do_title(); + $this->do_description(); + echo '
    '; + ?> +
    +
    + do_installer_action(); + $link = $this->get_product_link(); + if ( $link ) { + printf( + '%s', + esc_url( $link ), + esc_html__( 'Learn More', 'easy-digital-downloads' ) + ); + } + ?> +
    +
    + +

    + get_title( $link ) ); ?> +

    + product->heading ) ? $this->product->heading : $this->product->title; + $url = false; + if ( $link ) { + $url = $this->get_product_link(); + } + if ( $url ) { + return sprintf( + '%s', + esc_url( $url ), + esc_html( $title ) + ); + } + + return esc_html( $title ); + } + + /** + * Gets the product link. + * + * @since 3.3.0 + * @return string|false + */ + private function get_product_link() { + if ( empty( $this->product->slug ) ) { + return false; + } + + return 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 + ); + } + + /** + * Outputs the extension image. + * + * @since 3.1.1 + * @return void + */ + private function do_image() { + if ( empty( $this->product->image ) ) { + return; + } + ?> +
    + +
    + product->icon ) ) { + return; + } + ?> +
    + + do_recommended(); ?> +
    + 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 ); ?> +
    +
    + get_product_terms(), true ) ) { + return; + } + ?> + + 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 = $this->get_product_terms(); + if ( ! empty( $this->product->tab ) ) { + $terms[] = $this->product->tab; + } + + return implode( ',', array_map( 'strtolower', array_filter( $terms ) ) ); + } + + /** + * Gets the product terms for a card. + * + * @since 3.2.0 + * @return array + */ + private function get_product_terms() { + if ( ! empty( $this->product->terms ) ) { + return array_keys( (array) $this->product->terms ); + } + + return array(); + } +} 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/Emails.php b/src/Admin/Extensions/Emails.php new file mode 100644 index 00000000000..052a7a9cee9 --- /dev/null +++ b/src/Admin/Extensions/Emails.php @@ -0,0 +1,16 @@ +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; + } + + return array_key_first( $array ); + } + + /** + * 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; + } + ?> + + can_show_product_section() ) { + $classes[] = 'has-product-education'; + } + + return $classes; + } +} diff --git a/src/Admin/Extensions/ExtensionPage.php b/src/Admin/Extensions/ExtensionPage.php new file mode 100644 index 00000000000..2154d6063c4 --- /dev/null +++ b/src/Admin/Extensions/ExtensionPage.php @@ -0,0 +1,285 @@ + +
    +
    +
    +
    +
    +

    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; + } + + // If this is Lite, and there is a pass active, mention that (Pro) is needed to install addons. + if ( $this->pass_manager->has_pass() ) { + $url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + ) + ); + + wp_kses_post( + printf( + /* translators: 1: pass name, 2: opening anchor tag, 3: closing anchor tag. */ + __( 'Using the 1-Click Installation feature requires Easy Digital Downloads (Pro), which you have access to with your %1$s. %2$sInstall (Pro) now%3$s.', 'easy-digital-downloads' ), + $this->pass_manager->get_pass_name(), + '', + '' + ) + ); + + 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', + ); + } + + /** + * 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 can show two cases, since you can have a pass activated on lite, but lite cannot install addons. + if ( $this->pass_manager->has_pass() && ! edd_is_pro() ) { + $button['button_text'] = __( 'EDD (Pro) Required', 'easy-digital-downloads' ); + $button['disabled'] = true; + unset( $button['href'] ); + } else { + $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..57172973e11 --- /dev/null +++ b/src/Admin/Extensions/Extension_Manager.php @@ -0,0 +1,542 @@ +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_SPECIAL_CHARS ), + ) + ); + } + + /** + * 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'] ) : ''; + $plugin = ! empty( $_POST['plugin'] ) ? esc_url( $_POST['plugin'] ) : ''; + $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 + ? sprintf( + /* translators: 1: opening anchor tag, do not translate, 2: closing anchor tag, do not translate. */ + __( 'Could not install the plugin. Please %1$sdownload%2$s and install it manually via Plugins > Add New.', 'easy-digital-downloads' ), + ! empty( $plugin ) ? '' : '', + ! empty( $plugin ) ? '' : '' + ) + : 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' ), + '', + '' + ); + + if ( 'plugin' !== $type ) { + $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 The plugin basename. + * @return false|string + */ + public 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..5099782559a --- /dev/null +++ b/src/Admin/Extensions/ExtensionsAPI.php @@ -0,0 +1,341 @@ +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 ) { + $recommended = array(); + $personal_pass = array(); + $extended_pass = array(); + $pro_pass = array(); + $all_access_pass = array(); + $pass_manager = $this->get_pass_manager(); + foreach ( $all_product_data as $item_id => $item ) { + if ( ! empty( $item->categories ) ) { + if ( ! in_array( 1592, $item->categories, true ) ) { + continue; + } + + $item->pass_id = $this->get_pass_id( $item->categories, $pass_manager ); + if ( ! empty( $item->tags ) && in_array( 2333, $item->tags, true ) ) { + $recommended[ $item_id ] = $this->get_item_data( $item ); + } elseif ( $pass_manager::PERSONAL_PASS_ID === $item->pass_id ) { + $personal_pass[ $item_id ] = $this->get_item_data( $item ); + } elseif ( $pass_manager::EXTENDED_PASS_ID === $item->pass_id ) { + $extended_pass[ $item_id ] = $this->get_item_data( $item ); + } elseif ( $pass_manager::PROFESSIONAL_PASS_ID === $item->pass_id ) { + $pro_pass[ $item_id ] = $this->get_item_data( $item ); + } else { + $all_access_pass[ $item_id ] = $this->get_item_data( $item ); + } + } + } + + return $recommended + $personal_pass + $extended_pass + $pro_pass + $all_access_pass; + } + + /** + * Gets the pass ID required to be able to install the extension. + * + * @since 3.2.2 + * @param array $categories The extension categories. + * @param Pass_Manager $pass_manager The pass manager. + * @return string + */ + private function get_pass_id( $categories, $pass_manager ) { + if ( in_array( $pass_manager->categories[ $pass_manager::PERSONAL_PASS_ID ], $categories, true ) ) { + return $pass_manager::PERSONAL_PASS_ID; + } + if ( in_array( $pass_manager->categories[ $pass_manager::EXTENDED_PASS_ID ], $categories, true ) ) { + return $pass_manager::EXTENDED_PASS_ID; + } + if ( in_array( $pass_manager->categories[ $pass_manager::PROFESSIONAL_PASS_ID ], $categories, true ) ) { + return $pass_manager::PROFESSIONAL_PASS_ID; + } + + return $pass_manager::ALL_ACCESS_PASS_ID; + } + + /** + * 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 = new \EDD\Utils\RemoteRequest( $url ); + + // If there was an API error, set option and return false. + if ( is_wp_error( $request->body ) || ( 200 !== $request->code ) ) { + 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( $request->body ); + $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 ) { + + if ( ! isset( $item->pass_id ) ) { + $pass_manager = $this->get_pass_manager(); + $item->pass_id = $this->get_pass_id( $item->categories, $pass_manager ); + } + + 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, + 'pass_id' => ! empty( $item->pass_id ) ? $item->pass_id : '', + ); + } + + /** + * 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']; + } + + /** + * Gets the pass manager. + * + * @since 3.3.0 + * @return Pass_Manager + */ + private function get_pass_manager() { + if ( is_null( $this->pass_manager ) ) { + $this->pass_manager = new Pass_Manager(); + } + + return $this->pass_manager; + } +} diff --git a/src/Admin/Extensions/Legacy.php b/src/Admin/Extensions/Legacy.php new file mode 100644 index 00000000000..3c0a02d0b58 --- /dev/null +++ b/src/Admin/Extensions/Legacy.php @@ -0,0 +1,227 @@ + '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'] ) ) { + + $this->maybe_do_notification( $extension ); + + if ( ! empty( $extension['on_deactivate'] ) && is_callable( $extension['on_deactivate'] ) ) { + add_action( "deactivate_{$extension['basename']}", $extension['on_deactivate'] ); + } + 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 The plugin links. + * @param string $plugin_file The plugin file. + * @return array + */ + public function update_plugin_links( $links, $plugin_file ) { + $links['activate'] = __( 'Inactive — Part of EDD', 'easy-digital-downloads' ); + + return $links; + } + + /** + * Updates the auto register option and emails when Auto Register is deactivated. + * + * @since 3.3.0 + * @return void + */ + public function deactivate_auto_register() { + $auto_register = new Legacy\AutoRegister(); + $auto_register->update(); + } + + /** + * 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( + 'notification-id' => 'mp-legacy-notice', + 'name' => 'Manual Purchases', + 'basename' => 'edd-manual-purchases/edd-manual-purchases.php', + 'option' => 'edd_manual_purchases_license_active', + ), + 'edd-downloads-as-services' => array( + 'notification-id' => 'das-legacy-notice', + 'name' => 'Downloads as Services', + 'basename' => 'edd-downloads-as-services/edd-downloads-as-services.php', + ), + 'edd-disable-purchase-receipt' => array( + 'notification-id' => 'dpr-legacy-notice', + 'name' => 'Disable Purchase Receipt', + 'basename' => 'edd-disable-purchase-receipt/edd-disable-purchase-receipt.php', + 'on_deactivate' => array( $this, 'disable_order_receipt' ), + ), + 'edd-auto-register' => array( + 'notification-id' => 'ar-legacy-notice', + 'name' => 'Auto Register', + 'basename' => 'edd-auto-register/edd-auto-register.php', + 'on_deactivate' => array( $this, 'deactivate_auto_register' ), + 'content' => __( 'Auto Register has been merged into Easy Digital Downloads. It has been deactivated and you can safely delete the Auto Register plugin. Please review your new user emails to ensure that any customizations were retained during the migration.', 'easy-digital-downloads' ), + 'custom_buttons' => array( + array( + 'text' => __( 'View Emails', 'easy-digital-downloads' ), + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-emails', + ) + ), + ), + ), + ), + ); + } + + /** + * Updates the order receipt email when "Disable Purchase Receipt" is deactivated automatically. + * + * @since 3.3.0 + * @return void + */ + public function disable_order_receipt() { + $email = edd_get_email_by( 'email_id', 'order_receipt' ); + edd_update_email( + $email->id, + array( + 'status' => 0, + ) + ); + } + + /** + * Whether the plugin should be deactivated. + * + * @since 3.1.1 + * @param string $basename The plugin basename. + * @return bool + */ + protected function should_deactivate( $basename ) { + return true; + } + + /** + * If set, adds an EDD notification. + * + * @since 3.3.0 + * @param array $extension The array of extension data. + * @return void + */ + private function maybe_do_notification( $extension ) { + // If a legacy extension has a notification ID, then add a local notification. + if ( empty( $extension['notification-id'] ) ) { + return; + } + EDD()->notifications->maybe_add_local_notification( $this->get_notification_args( $extension ) ); + } + + /** + * Retrieves the notification arguments for a given extension. + * + * @since 3.3.0 + * @param array $extension The array of extension data. + * @return array The notification arguments for the extension. + */ + private function get_notification_args( $extension ) { + return wp_parse_args( + $extension, + array( + 'remote_id' => $extension['notification-id'], + 'type' => 'info', + 'title' => sprintf( + /* translators: %s: name of the extension. */ + __( '%s is now part of EDD!', 'easy-digital-downloads' ), + $extension['name'] + ), + 'content' => sprintf( + /* translators: %s: name of the extension. */ + __( 'The functionality of %1$s has been merged into Easy Digital Downloads. It has been deactivated and you can safely delete the %2$s plugin.', 'easy-digital-downloads' ), + $extension['name'], + $extension['name'] // This is the same as the previous placeholder, but it's necessary because the original string has two placeholders. + ), + 'buttons' => $this->get_buttons( $extension ), + ) + ); + } + + /** + * Retrieves the buttons for a given extension. + * + * @since 3.3.0 + * @param array $extension The array of extension data. + * @return array The buttons for the extension. + */ + private function get_buttons( $extension ) { + $buttons = array( + array( + 'text' => __( 'View Plugins', 'easy-digital-downloads' ), + 'url' => add_query_arg( + array( + 's' => urlencode( $extension['name'] ), + ), + admin_url( 'plugins.php' ) + ), + ), + ); + + if ( empty( $extension['custom_buttons'] ) ) { + return $buttons; + } + + return array_merge( $buttons, $extension['custom_buttons'] ); + } +} diff --git a/src/Admin/Extensions/Legacy/AutoRegister.php b/src/Admin/Extensions/Legacy/AutoRegister.php new file mode 100644 index 00000000000..0925b41ea47 --- /dev/null +++ b/src/Admin/Extensions/Legacy/AutoRegister.php @@ -0,0 +1,165 @@ +update_user_email(); + $this->update_admin_email(); + } + + /** + * Update the user email. + * + * @since 3.3.0 + * @return void + */ + private function update_user_email() { + $template = edd_get_email_registry()->get_email_by_id( 'new_user' ); + if ( ! $template ) { + return; + } + $email = $template->get_email(); + if ( ! $email ) { + return; + } + $data = array(); + // Even though the data is in the email object, we are looking for the old options so we don't overwrite them if they were customized. + if ( empty( edd_get_option( 'edd_new_user_body' ) ) ) { + $data['content'] = $this->get_new_user_message(); + } + if ( empty( edd_get_option( 'edd_new_user_subject' ) ) ) { + $data['subject'] = $this->get_new_user_subject(); + } + if ( ! empty( edd_get_option( 'edd_auto_register_disable_user_email' ) ) ) { + $data['status'] = 0; + } + if ( ! empty( $data ) ) { + edd_update_email( $email->id, $data ); + } + edd_delete_option( 'edd_auto_register_disable_user_email' ); + } + + /** + * Update the admin email. + * + * @since 3.3.0 + * @return void + */ + private function update_admin_email() { + $template = edd_get_email_registry()->get_email_by_id( 'new_user_admin' ); + if ( ! $template ) { + return; + } + $email = $template->get_email(); + if ( ! $email ) { + return; + } + $data = array(); + // Even though the data is in the email object, we are looking for the old options so we don't overwrite them if they were customized. + if ( empty( edd_get_option( 'edd_new_user_admin_body' ) ) ) { + $data['content'] = $this->get_new_user_admin_message(); + } + if ( empty( edd_get_option( 'edd_new_user_admin_subject' ) ) ) { + $data['subject'] = $this->get_new_user_admin_subject(); + } + if ( ! empty( edd_get_option( 'edd_auto_register_disable_admin_email' ) ) ) { + $data['status'] = 0; + } + if ( ! empty( $data ) ) { + edd_update_email( $email->id, $data ); + } + edd_delete_option( 'edd_auto_register_disable_admin_email' ); + } + + /** + * Gets the new user message content from Auto Register. + * + * @since 3.3.0 + * @return string + */ + private function get_new_user_message() { + /* translators: %s: Email tag that will be replaced with the customer name */ + $message = sprintf( _x( 'Dear %s,', 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', 'easy-digital-downloads' ), '{name}' ) . "\n\n"; + $message .= __( 'Below are your login details:', 'easy-digital-downloads' ) . "\n\n"; + /* translators: %s: Email tag that will be replaced with the customer username */ + $message .= sprintf( __( 'Your Username: %s', 'easy-digital-downloads' ), '{username}' ) . "\r\n\r\n"; + $message .= '{password_link}' . "\r\n\r\n"; + $message .= '{login_link}' . "\r\n"; + + /** + * Optionally filters the email message. + * + * @param string $message + * @param string $first_name + * @param WP_User $user + * @param string $password + */ + return apply_filters( 'edd_auto_register_email_body', $message, false, false, false ); + } + + /** + * Gets the new user subject from Auto Register. + * + * @since 3.3.0 + * @return string + */ + private function get_new_user_subject() { + return apply_filters( + 'edd_auto_register_email_subject', + sprintf( + /* translators: %s: Site name email tag */ + __( '[%s] Login Details', 'easy-digital-downloads' ), + '{sitename}' + ) + ); + } + + /** + * Gets the new user admin message from Auto Register. + * + * @since 3.3.0 + * @return string + */ + private function get_new_user_admin_message() { + /* translators: %s: Email tag that will be replaced with the Site Name */ + $message = sprintf( _x( 'New user registration on your site %s:', 'Used to insert the {sitename} email tag into default email content.', 'easy-digital-downloads' ), '{sitename}' ) . "\r\n\r\n"; + /* translators: %s: Username email tag */ + $message .= sprintf( _x( 'Username: %s', 'Used to insert the {username} email tag into default email content.', 'easy-digital-downloads' ), '{username}' ) . "\r\n\r\n"; + /* translators: %s: User email email tag */ + $message .= sprintf( _x( 'Email: %s', 'Used to insert the {user_email} email tag into default email content.', 'easy-digital-downloads' ), '{user_email}' ) . "\r\n"; + + return $message; + } + + /** + * Gets the new user admin subject from Auto Register. + * + * @since 3.3.0 + * @return string + */ + private function get_new_user_admin_subject() { + /* translators: %s: Email tag that will be replaced with the Site Name */ + return sprintf( _x( '[%s] New User Registration', 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', 'easy-digital-downloads' ), '{sitename}' ); + } +} diff --git a/src/Admin/Extensions/Menu.php b/src/Admin/Extensions/Menu.php new file mode 100644 index 00000000000..98bb4e0ea2d --- /dev/null +++ b/src/Admin/Extensions/Menu.php @@ -0,0 +1,53 @@ + $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..d5f7be59df5 --- /dev/null +++ b/src/Admin/Extensions/Traits/Buttons.php @@ -0,0 +1,88 @@ +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; + } + ?> + + > + + + 2340 ); + } + + /** + * Updates the card configuration. + * + * @since 3.3.0 + * @param ProductData $product_data The extension data returned from the Products API. + * @return array + */ + protected function get_configuration( ProductData $product_data ) { + return array( + 'style' => 'overlay', + ); + } + + /** + * Update the button parameters. + * + * @since 3.3.0 + * @param ProductData $product_data The extension data returned from the Products API. + * @param bool|int $item_id The item ID. + * @return array + */ + protected function get_button_parameters( ProductData $product_data, $item_id = false ) { + $button = parent::get_button_parameters( $product_data, $item_id ); + + // If the extension is not active, return the button as is. + if ( ! $this->manager->is_plugin_active( $product_data->basename ) ) { + return $button; + } + + // If the extension is active and an update is available, link to the update screen. + if ( version_compare( $this->manager->get_plugin_version( $product_data->basename ), $product_data->version, '<' ) ) { + $button['button_text'] = __( 'Update Now', 'easy-digital-downloads' ); + $button['href'] = admin_url( 'update-core.php' ); + } + + return $button; + } +} diff --git a/src/Admin/Installers/Install_Skin.php b/src/Admin/Installers/Install_Skin.php new file mode 100644 index 00000000000..73ad05a3c91 --- /dev/null +++ b/src/Admin/Installers/Install_Skin.php @@ -0,0 +1,27 @@ + '', // 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 ) { + // Output 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 + * @since 3.3.4 Switched to using the EDD\Utils\FileSystem class. + * @param bool $enable True to enable maintenance mode, false to disable. + */ + public function maintenance_mode( $enable = false ) { + $file_system = FileSystem::get_fs(); + + $file = $file_system->abspath() . '.maintenance'; + if ( $enable ) { + //$this->skin->feedback( 'maintenance_start' ); + // Create maintenance file to signal that we are upgrading + $maintenance_string = ''; + $file_system->delete( $file ); + $file_system->put_contents( $file, $maintenance_string, FS_CHMOD_FILE ); + } elseif ( ! $enable && FileSystem::file_exists( $file ) ) { + //$this->skin->feedback( 'maintenance_end' ); + $file_system->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 ) && FileSystem::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 + * @since 3.3.4 Switched to using the EDD\Utils\FileSystem class. + * + * @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 ) { + $file_system = FileSystem::get_fs(); + + //$this->skin->feedback( 'unpack_package' ); + + $upgrade_folder = $file_system->wp_content_dir() . 'upgrade/'; + + // Clean up contents of upgrade directory beforehand. + $upgrade_files = $file_system->dirlist( $upgrade_folder ); + if ( ! empty( $upgrade_files ) ) { + foreach ( $upgrade_files as $file ) { + $file_system->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 ( $file_system->is_dir( $working_dir ) ) { + $file_system->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 ) ) { + $file_system->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 + * @since 3.3.4 Switched to using the EDD\Utils\FileSystem class. + * + * @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_theme_directories; + $file_system = FileSystem::get_fs(); + + $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']; + + 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( $file_system->dirlist( $remote_source ) ); + $remote_destination = $file_system->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 && $file_system->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( $file_system->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'] && FileSystem::file_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 = $file_system->dirlist( $remote_destination ); + + if ( ! empty( $_files ) ) { + $file_system->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 ( ! FileSystem::file_exists( $remote_destination ) ) { + if ( ! $file_system->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'] ) { + $file_system->delete( $remote_source, true ); + } + + return $result; + } + + // Clear the Working folder? + if ( $args['clear_working'] ) { + $file_system->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 @@ + array( 'render', 1 ), + ); + } + + /** + * Render the admin header. + * + * @since 3.3.0 + */ + public function render() { + if ( ! $this->can_render() ) { + return; + } + $number_notifications = EDD()->notifications->countActiveNotifications(); + + $is_single_view = $this->is_single_view(); + $page_title = $this->get_page_title(); + if ( ! empty( $page_title ) && empty( $is_single_view ) ) { + $this->print_style_script(); + } + ?> + +
    +
    + + + + + + + / + + < class="edd-header-page-title">> + + + +
    + +
    +
    +
    + maybe_do_product_navigation(); + + add_action( + 'admin_footer', + function () { + require_once EDD_PLUGIN_DIR . 'includes/admin/views/notifications.php'; + } + ); + } + + /** + * Check if the header can be rendered. + * + * @since 3.3.0 + * @return bool + */ + private function can_render() { + if ( ! edd_is_admin_page( '', '', false ) ) { + return false; + } + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false; + if ( $screen && $screen->is_block_editor() ) { + return false; + } + + return true; + } + + /** + * Gets the page title. + * + * @since 3.3.0 + * @return string + */ + private function get_page_title() { + $current_page = ! empty( $_GET['page'] ) ? $_GET['page'] : ''; + + $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-emails': + $page_title = __( 'Emails', '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( $current_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; + } + + return apply_filters( 'edd_settings_page_title', $page_title, $current_page, $this->is_single_view() ); + } + + /** + * Check if the current view is a single view. + * + * @since 3.3.0 + * @return bool + */ + private function is_single_view() { + return (bool) apply_filters( 'edd_admin_is_single_view', ! empty( $_GET['view'] ) && ! isset( $_GET['s'] ) ); + } + + /** + * Print the style and script. + * + * @since 3.3.0 + */ + private function print_style_script() { + ?> + + + can_do_product_tabs() ) { + edd_display_product_tabs(); + } + } + + /** + * Check if we can display the product navigation. + * + * @since 3.3.0 + * @return bool + */ + private function can_do_product_tabs() { + $screen = get_current_screen(); + if ( 'download' !== $screen->post_type ) { + return false; + } + if ( 'edit' === $screen->base ) { + return true; + } + + $taxonomy = filter_input( INPUT_GET, 'taxonomy', FILTER_SANITIZE_SPECIAL_CHARS ); + + // Bail if not viewing a taxonomy. + if ( empty( $taxonomy ) ) { + return false; + } + + return in_array( $taxonomy, get_object_taxonomies( 'download' ), true ); + } +} diff --git a/src/Admin/Menu/LinkClass.php b/src/Admin/Menu/LinkClass.php new file mode 100644 index 00000000000..179b24e3ac2 --- /dev/null +++ b/src/Admin/Menu/LinkClass.php @@ -0,0 +1,62 @@ +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/Menu/Pages.php b/src/Admin/Menu/Pages.php new file mode 100644 index 00000000000..19a93dd7111 --- /dev/null +++ b/src/Admin/Menu/Pages.php @@ -0,0 +1,161 @@ + $page ) { + add_submenu_page( + self::get_parent_slug(), + $page['page_title'], + $page['menu_title'], + $page['capability'], + $slug, + $page['callback'] + ); + } + + self::register_upgrade_page(); + self::add_to_dashboard(); + } + + /** + * Gets the list of EDD admin page slugs. + * + * @since 3.3.0 + * @return array + */ + public static function get_pages() { + return array_keys( self::define_pages() ); + } + + /** + * Defines the EDD admin pages. + * + * @since 3.3.0 + * @return array + */ + private static function define_pages() { + return array( + 'edd-payment-history' => array( + 'page_title' => __( 'Orders', 'easy-digital-downloads' ), + 'menu_title' => __( 'Orders', 'easy-digital-downloads' ), + 'capability' => 'edit_shop_payments', + 'callback' => 'edd_payment_history_page', + ), + 'edd-customers' => array( + 'page_title' => __( 'Customers', 'easy-digital-downloads' ), + 'menu_title' => __( 'Customers', 'easy-digital-downloads' ), + 'capability' => apply_filters( 'edd_view_customers_role', 'view_shop_reports' ), + 'callback' => 'edd_customers_page', + ), + 'edd-discounts' => array( + 'page_title' => __( 'Discounts', 'easy-digital-downloads' ), + 'menu_title' => __( 'Discounts', 'easy-digital-downloads' ), + 'capability' => 'manage_shop_discounts', + 'callback' => 'edd_discounts_page', + ), + 'edd-reports' => array( + 'page_title' => __( 'Reports', 'easy-digital-downloads' ), + 'menu_title' => __( 'Reports', 'easy-digital-downloads' ), + 'capability' => 'view_shop_reports', + 'callback' => 'edd_reports_page', + ), + 'edd-settings' => array( + 'page_title' => __( 'EDD Settings', 'easy-digital-downloads' ), + 'menu_title' => __( 'Settings', 'easy-digital-downloads' ), + 'capability' => 'manage_shop_settings', + 'callback' => array( '\\EDD\\Admin\\Settings\\Screen', 'render' ), + ), + 'edd-emails' => array( + 'page_title' => __( 'EDD Emails', 'easy-digital-downloads' ), + 'menu_title' => self::mark_new( __( 'Emails', 'easy-digital-downloads' ) ), + 'capability' => 'manage_shop_settings', + 'callback' => array( '\\EDD\\Admin\\Emails\\Screen', 'render' ), + ), + 'edd-tools' => array( + 'page_title' => __( 'EDD Tools', 'easy-digital-downloads' ), + 'menu_title' => __( 'Tools', 'easy-digital-downloads' ), + 'capability' => 'manage_shop_settings', + 'callback' => array( '\\EDD\\Admin\\Tools\\Screen', 'render' ), + ), + ); + } + + /** + * Registers the hidden upgrades page. + * + * @since 3.3.0 + */ + private static function register_upgrade_page() { + add_submenu_page( + 'index.php', + __( 'EDD Upgrades', 'easy-digital-downloads' ), + __( 'EDD Upgrades', 'easy-digital-downloads' ), + 'manage_shop_settings', + 'edd-upgrades', + 'edd_upgrades_screen' + ); + add_action( + 'admin_head', + function () { + remove_submenu_page( 'index.php', 'edd-upgrades' ); + } + ); + } + + /** + * Add our reports link in the main Dashboard menu. + * + * @since 3.3.0 + */ + private static function add_to_dashboard() { + global $submenu; + + $submenu['index.php'][] = array( // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + __( 'Store Reports', 'easy-digital-downloads' ), + 'view_shop_reports', + self::get_parent_slug() . '&page=edd-reports', + ); + } + + /** + * Get the parent slug for the EDD submenu pages. + * + * @since 3.3.0 + * @return string + */ + private static function get_parent_slug() { + return 'edit.php?post_type=download'; + } + + /** + * Adds an indicator to mark a new menu item. + * + * @since 3.3.0 + * @return string + */ + private static function mark_new( $title ) { + return sprintf( + '%s %s', + $title, + __( 'NEW!', 'easy-digital-downloads' ) + ); + } +} diff --git a/src/Admin/Menu/SecondaryNavigation.php b/src/Admin/Menu/SecondaryNavigation.php new file mode 100644 index 00000000000..6d1c21a4bae --- /dev/null +++ b/src/Admin/Menu/SecondaryNavigation.php @@ -0,0 +1,229 @@ +tabs = self::get_tabs( $tabs, $page ); + $this->page = $page; + $this->args = $args; + } + + /** + * Render the tabs. + * + * @since 3.3.0 + */ + public function render() { + ?> +
    + + args['show_search'] ) ) { + if ( isset( $_REQUEST['s'] ) && strlen( $_REQUEST['s'] ) ) { + echo ''; + printf( + /* translators: %s: Search query. */ + __( 'Search results for: %s', 'easy-digital-downloads' ), + '' . esc_html( $_REQUEST['s'] ) . '' + ); + echo ''; + } + } + ?> +
    + args['active_tab'] ) ) { + return $this->args['active_tab']; + } + + $tab = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( $tab && array_key_exists( $tab, $this->tabs ) ) { + return $tab; + } + + return array_key_first( $this->tabs ); + } + + /** + * Get the nav classes. + * + * @since 3.3.0 + * @return array + */ + private function get_nav_classes(): array { + $nav_classes = array( + 'edd-nav', + "{$this->page}-nav", + ); + if ( ! empty( $this->args['legacy'] ) ) { + $nav_classes = array_merge( $nav_classes, array( 'nav-tab-wrapper', 'edd-nav-tab-wrapper' ) ); + } + + return $nav_classes; + } + + /** + * Get the li classes. + * + * @since 3.3.0 + * @param string $slug The tab slug. + * @return array + */ + private function get_li_classes( $slug ): array { + $classes = array( 'edd-nav__tabs--item' ); + if ( $this->get_current_tab() === $slug ) { + $classes[] = 'active'; + } + + return $classes; + } + + /** + * Get the tab classes. + * + * @since 3.3.0 + * @param string $slug The tab slug. + * @return array + */ + private function get_tab_classes( string $slug ): array { + $classes = array( 'tab' ); + if ( ! empty( $this->args['legacy'] ) ) { + $classes[] = 'nav-tab'; + if ( $this->get_current_tab() === $slug ) { + $classes[] = 'nav-tab-active'; + } + } + + return $classes; + } + + /** + * Convert an array of CSS classes to a string. + * + * @since 3.3.0 + * @param array $classes The CSS classes. + * @return string + */ + private function css_classes_to_string( array $classes ): string { + return implode( ' ', array_map( 'sanitize_html_class', array_filter( $classes ) ) ); + } + + /** + * Get the tab URL. + * + * @since 3.3.0 + * @param string $slug The tab slug. + * @return string + */ + private function get_tab_url( string $slug, $data = null ): string { + if ( is_array( $data ) && ! empty( $data['url'] ) ) { + return $data['url']; + } + + $args = array( + 'page' => sanitize_key( $this->page ), + ); + if ( array_key_first( $this->tabs ) !== $slug ) { + $args['tab'] = sanitize_key( $slug ); + } + + return edd_get_admin_url( $args ); + } + + /** + * Get the tab name. + * + * @since 3.3.0 + * @param string|array $data The tab data. + * @return string + */ + private function get_tab_name( $data ) { + return is_array( $data ) ? $data['name'] : $data; + } +} diff --git a/src/Admin/Notifications/GBLegacy.php b/src/Admin/Notifications/GBLegacy.php new file mode 100644 index 00000000000..7126df5859d --- /dev/null +++ b/src/Admin/Notifications/GBLegacy.php @@ -0,0 +1,146 @@ + __( 'Please Update Your Settings', 'easy-digital-downloads' ), + 'content' => __( + 'We recently updated our list of regions for the United Kingdom. We have detected that your store is using an outdated region for the business settings or tax rates. Please review these settings and update them if needed.', + 'easy-digital-downloads' + ), + 'buttons' => self::get_buttons(), + ); + } + + /** + * Checks if the base state is a legacy region. + * + * @since 3.3.0 + * @return bool True if the base state is a legacy region, false otherwise. + */ + private static function base_state_is_legacy() { + if ( 'GB' !== edd_get_option( 'base_country', 'US' ) ) { + return false; + } + + $base_state = edd_get_option( 'base_state', '' ); + if ( empty( $base_state ) ) { + return false; + } + + return (bool) array_key_exists( $base_state, self::get_legacy_states() ); + } + + /** + * Checks if any tax rate is using a legacy region. + * + * @since 3.3.0 + * @return bool True if a tax rate is using a legacy region, false otherwise. + */ + private static function tax_rate_uses_legacy_region() { + return ! empty( + edd_get_tax_rates( + array( + 'name' => 'GB', + 'number' => 1, + 'scope' => 'region', + 'description__in' => array_keys( self::get_legacy_states() ), + 'status' => 'active', + ) + ) + ); + } + + /** + * Gets the buttons for the notification. + * + * @since 3.3.0 + * @return array The buttons for the notification. + */ + private static function get_buttons() { + $buttons = array(); + if ( self::base_state_is_legacy() ) { + $buttons[] = array( + 'text' => __( 'Update Business Region', 'easy-digital-downloads' ), + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'general', + ) + ), + 'type' => 'primary', + ); + } + if ( self::tax_rate_uses_legacy_region() ) { + $buttons[] = array( + 'text' => __( 'Review Tax Rates', 'easy-digital-downloads' ), + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'taxes', + 'section' => 'rates', + ) + ), + ); + } + + return $buttons; + } + + /** + * Gets the legacy states. + * + * @since 3.3.0 + * @return array The legacy states. + */ + private static function get_legacy_states() { + return include EDD_PLUGIN_DIR . 'i18n/states-gb-legacy.php'; + } +} diff --git a/src/Admin/Notifications/Loader.php b/src/Admin/Notifications/Loader.php new file mode 100644 index 00000000000..8fa89bb1855 --- /dev/null +++ b/src/Admin/Notifications/Loader.php @@ -0,0 +1,65 @@ + 'add_events', + ); + } + + /** + * Adds events for the Notifications Loader. + * + * @return void + */ + public function add_events() { + $notification_classes = $this->get_notifications(); + foreach ( $notification_classes as $notification_class ) { + if ( ! is_subclass_of( $notification_class, Notification::class ) ) { + continue; + } + + $notification_class::add(); + } + } + + /** + * Retrieves the notifications. + * + * This method is responsible for retrieving the notifications. + * + * @since 3.3.0 + * @return array The notifications. + */ + private function get_notifications() { + return array( + GBLegacy::class, + ); + } +} diff --git a/src/Admin/Notifications/Notification.php b/src/Admin/Notifications/Notification.php new file mode 100644 index 00000000000..889a727de8d --- /dev/null +++ b/src/Admin/Notifications/Notification.php @@ -0,0 +1,111 @@ +notifications->maybe_add_local_notification( $args ); + } + + /** + * Determines whether the notification exists. + * + * @since 3.3.0 + * @return bool True if the notification exists, false otherwise. + */ + protected static function notification_exists() { + $query = new \EDD\Database\Queries\Notification(); + + return (bool) $query->get_item_by( 'remote_id', static::$id ); + } + + /** + * Registers the notification. + * + * @since 3.3.0 + * @return array The registered notification. + */ + abstract protected static function register(): array; + + /** + * Determines whether the notification can be registered. + * + * @since 3.3.0 + * @return bool True if the notification can be registered, false otherwise. + */ + abstract public static function can_register(): bool; + + /** + * Retrieves the notification arguments. + * + * @since 3.3.0 + * @return array The notification arguments. + */ + private static function get_notification_args(): array { + $args = static::register(); + if ( empty( $args ) ) { + return array(); + } + + return wp_parse_args( + $args, + array( + 'type' => static::$type, + 'remote_id' => static::$id, + ) + ); + } +} diff --git a/src/Admin/Onboarding/Ajax.php b/src/Admin/Onboarding/Ajax.php new file mode 100644 index 00000000000..e435be99447 --- /dev/null +++ b/src/Admin/Onboarding/Ajax.php @@ -0,0 +1,254 @@ + '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 ), + 'wp_ajax_edd_onboarding_save_email' => 'save_email', + ); + } + + /** + * 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 ) ); + } + + if ( isset( $_REQUEST['auto_register'] ) ) { + edd_update_option( 'logged_in_only', 'auto' ); + } + + 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 + ); + } + + public function save_email() { + if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'edd_onboarding_wizard' ) ) { + exit; + } + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + exit; + } + + $settings = array( + 'email_logo', + 'from_name', + 'from_email', + ); + + foreach ( $settings as $setting ) { + $value = isset( $_POST[ $setting ] ) ? sanitize_text_field( wp_unslash( $_POST[ $setting ] ) ) : ''; + edd_update_option( $setting, $value ); + } + + if ( isset( $_POST['content'] ) ) { + $email = edd_get_email( 'order_receipt' ); + if ( $email ) { + edd_update_email( + $email->id, + array( + 'content' => wp_kses_post( wp_unslash( $_POST['content'] ) ), + ) + ); + } + } + + exit; + } + + /** + * 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 ) ); ?> + + + + +
    + get()['main']; + + ?> + + +
    + + + + +
      +
    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' => __( '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 Tools', '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', + ), + ), + array( + 'name' => __( 'Conversion Tools', 'easy-digital-downloads' ), + 'description' => __( 'Get the #1 conversion optimization plugin to convert your growing website traffic into subscribers, leads and sales.', 'easy-digital-downloads' ), + 'prechecked' => true, + 'plugin_name' => 'OptinMonster', + 'plugin_file' => 'optinmonster/optin-monster-wp-api.php', + 'plugin_zip' => 'https://downloads.wordpress.org/plugin/optinmonster.zip', + 'plugin_url' => 'https://wordpress.org/plugins/optinmonster/', + 'action' => 'install', + 'conflicts' => array(), + ), + ); + + // 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' ) ); + 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' ); + if ( $this->has_onboarding_been_completed() ) { + remove_submenu_page( 'edit.php?post_type=download', 'edd-onboarding-wizard' ); + } + } + + /** + * 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' => sprintf( + /* translators: %s: The company name of the person giving the testimonial */ + _x( + 'Podcast Coach - %s', + 'Context: The title/role of the person giving the testimonial. Example: Podcast Coach - How I Built It. The company name remains untranslated', + 'easy-digital-downloads' + ), + 'How I Built It' + ), + 'content' => _x( + '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.', + 'Context: Direct quote used as a testimonial for Easy Digital Downloads', + 'easy-digital-downloads' + ), + 'avatar' => 'joe.jpg', + 'stars' => 5, + ), + array( + 'name' => 'Nicolas Martin', + 'company' => sprintf( + /* translators: %s: The company name of the person giving the testimonial */ + _x( + 'Founder - %s', + 'Context: The title/role of the person giving the testimonial. Example: Flea Market Insiders. The company name remains untranslated', + 'easy-digital-downloads' + ), + 'Flea Market Insiders' + ), + 'content' => _x( + '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%.', + 'Context: Direct quote used as a testimonial for Easy Digital Downloads', + 'easy-digital-downloads' + ), + 'avatar' => 'nicolas.jpg', + 'stars' => 5, + ), + array( + 'name' => 'Bob Dunn', + 'company' => sprintf( + /* translators: %s: The company name of the person giving the testimonial */ + _x( + 'Community Leader & Podcaster - %s', + 'Context: The title/role of the person giving the testimonial. Example: Community Leader - BobWP. The company name remains untranslated', + 'easy-digital-downloads' + ), + 'BobWP' + ), + 'content' => _x( + '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.', + 'Context: Direct quote used as a testimonial for Easy Digital Downloads', + 'easy-digital-downloads' + ), + '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/Handler.php b/src/Admin/PassHandler/Handler.php new file mode 100644 index 00000000000..bff4efe2b44 --- /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..125291b1d8e --- /dev/null +++ b/src/Admin/PassHandler/Settings.php @@ -0,0 +1,207 @@ +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(); + ?> + + + + + + + + + + + + __( '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 we're running the Pro version of EDD, we don't need to show this. + if ( edd_is_pro() ) { + return; + } + + // The user could have the Lite version, but with a Pass activated on extensions, so we need to check for that. + $pass_manager = new \EDD\Admin\Pass_Manager(); + if ( $pass_manager->has_pass() ) { + 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..3314f634aa4 --- /dev/null +++ b/src/Admin/Pointers.php @@ -0,0 +1,129 @@ + '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; + } + $rtl = is_rtl() ? '-rtl' : ''; + wp_enqueue_style( 'edd-pointers', EDD_PLUGIN_URL . "assets/css/edd-admin-pointers{$rtl}.min.css", array( 'wp-pointer' ), EDD_VERSION ); + 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/About.php b/src/Admin/Promos/About.php new file mode 100644 index 00000000000..8c3c4318829 --- /dev/null +++ b/src/Admin/Promos/About.php @@ -0,0 +1,868 @@ + 'add_body_class', + 'edd_settings_page_title' => 'page_title', + 'admin_menu' => array( 'register_page', 99 ), + ); + } + + /** + * Adds the "edd-about" body class to the EDD About Us page. + * + * @since 3.2.4 + * + * @param string $classes The current body classes. + * @return string + */ + public function add_body_class( $classes ) { + if ( $this->is_about_us_page() ) { + $classes .= ' edd-about'; + } + + return $classes; + } + + /** + * Changes the page title for the EDD About Us page and the Getting Started Page + * + * @since 3.2.4 + * + * @param string $title The current page title. + * @return string + */ + public function page_title( $title ) { + if ( $this->is_about_us_page() ) { + $tabs = $this->setup_tabs(); + + $title = $tabs[ $this->get_current_tab() ]; + } + + return $title; + } + + /** + * Register the About Us page. + * + * @since 3.2.4 + */ + public function register_page() { + add_submenu_page( + 'edit.php?post_type=download', + __( 'About Easy Digital Downloads', 'easy-digital-downloads' ), + __( 'About Us', 'easy-digital-downloads' ), + 'manage_shop_settings', + 'edd-about', + array( $this, 'render_page' ) + ); + } + + /** + * Render the About Us page. + * + * @since 3.2.4 + */ + public function render_page() { + $this->manager = new Extension_Manager(); + $this->manager->enqueue(); + + $this->setup_tabs(); + + $navigation = new \EDD\Admin\Menu\SecondaryNavigation( + $this->tabs, + 'edd-about' + ); + $navigation->render(); + ?> +
    + render_tab( $this->get_current_tab() ); ?> +
    + tabs ) ) { + return $this->tabs; + } + + $this->tabs = array( + 'general' => __( 'About Us', 'easy-digital-downloads' ), + 'getting_started' => __( 'Getting Started', 'easy-digital-downloads' ), + ); + + return $this->tabs; + } + + /** + * Check if we are on the About Us page. + * + * @since 3.2.4 + * + * @return bool + */ + private function is_about_us_page() { + $screen = get_current_screen(); + if ( 'download_page_edd-about' === $screen->id ) { + return true; + } + + return false; + } + + /** + * Get the current tab. + * + * @since 3.2.4 + * + * @return string + */ + private function get_current_tab() { + $tab = isset( $_GET['tab'] ) ? sanitize_text_field( $_GET['tab'] ) : 'general'; + if ( ! array_key_exists( $tab, $this->tabs ) ) { + $tab = $this->default_tab; + } + + return $tab; + } + + /** + * Render the specific tab section content. + * + * @since 3.2.4 + * + * @param string $tab The tab to render. + */ + private function render_tab( $tab ) { + switch ( $tab ) { + case 'getting_started': + $this->render_tab_getting_started(); + break; + case 'general': + default: + $this->render_tab_general(); + break; + } + } + + /** + * Renders the standard About Us tab content. + * + * @since 3.2.4 + */ + private function render_tab_general() { + ?> +
    + +
    +

    + +

    +

    + +

    +

    + +

    +

    + +

    +

    + +

    +
    + +
    +
    + <?php esc_attr_e( 'The Awesome Motive Team photo', 'easy-digital-downloads' ); ?> +
    +
    +
    +
    +
    + +
    + get_am_plugins(); + $can_install_plugins = current_user_can( 'install_plugins' ); + ?> +
    +
    + $details ) : + $plugin_data = $this->get_plugin_data( $plugin, $details, $all_plugins ); + ?> +
    +
    +
    + + <?php echo esc_attr( $plugin_data['details']['name'] ); ?> + + +
    + +
    +

    + +

    +
    +
    +
    +
    + ' . wp_kses_post( $plugin_data['status_text'] ) . '' + ); + ?> +
    +
    +
    + + + + + + + manager->button( $plugin_data['button_parameters'] ); + } + ?> +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    +

    + +

    +

    + +

    +

    + +

    + +
    + +    +
    +
    + +
    +
    + <?php esc_attr_e( 'Welcome to Easy Digital Downloads', 'easy-digital-downloads' ); ?> +
    +
    + +
    + has_pass() ) { + ?> +
    + +
    +

    + +

    + +

    + Upgrade to Easy Digital Downloads (Pro) to unlock all the awesome features and experience why Easy Digital Downloads is regarded as the best eCommerce plugin for digital products and services.', 'easy-digital-downloads' ), + array( + 'br' => array(), + 'strong' => array(), + ) + ); + ?> +

    + +

    + 450+ five star ratings (%s) and over 50,000+ professionals and creators use it to run their businesses and projects.', 'easy-digital-downloads' ), + array( + 'strong' => array(), + ) + ), + '' . + '' . + '' . + '' . + '' + ); + ?> +

    +
    + +
    +
    +
    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    + +
    + +

    + 'about-us', + 'utm-content' => 'getting-started', + ) + ); + echo sprintf( + /* translators: 1: opening link tag, 2. closing link tag. */ + __( '%1$sUpgrade to Pro Today%2$s', 'easy-digital-downloads' ), + '', + '' + ); + ?> +

    + +

    + 50% off regular price, automatically applied at checkout.', 'easy-digital-downloads' ), + array( + 'span' => array( + 'class' => array(), + ), + ) + ); + ?> +

    +
    + +
    + + + esc_html__( 'An Introduction to Easy Digital Downloads', 'easy-digital-downloads' ), + 'url' => 'https://easydigitaldownloads.com/docs/easy-digital-downloads-introduction/', + 'description' => esc_html__( 'If you\'re already generally familiar with eCommerce in WordPress then this document should help you get up to speed with Easy Digital Downloads quite quickly. Unless otherwise noted, everything in this document deals with core features.', 'easy-digital-downloads' ), + 'image' => 'introduction-to-edd.svg', + ), + array( + 'title' => esc_html__( 'How to Install and Activate EDD Extensions', 'easy-digital-downloads' ), + 'url' => 'https://easydigitaldownloads.com/docs/how-do-i-install-an-extension/', + 'description' => esc_html__( 'Would you like to access Easy Digital Downloads extensions to extend the functionality of your store? Each EDD pass level comes with its own set of extensions to help you get the most out of your store.', 'easy-digital-downloads' ), + 'image' => 'how-to-install-activate.svg', + ), + array( + 'title' => esc_html__( 'Using the Included Easy Digital Downloads Blocks', 'easy-digital-downloads' ), + 'url' => 'https://easydigitaldownloads.com/docs/easy-digital-downloads-blocks/', + 'description' => esc_html__( 'Creating your store has never been easier than when using the included Easy Digital Downloads Blocks, fully integrated with the WordPress Block Editor. Learn about what blocks come with Easy Digital Downloads and how you can use them on your store.', 'easy-digital-downloads' ), + 'image' => 'using-edd-blocks.svg', + ), + array( + 'title' => esc_html__( 'Configuring Cache for Easy Digital Downloads', 'easy-digital-downloads' ), + 'url' => 'https://easydigitaldownloads.com/docs/configure-cache/', + 'description' => esc_html__( 'Caching plugins and services are designed to help ensure your site responds as quickly as possible. We understand that a fast store converts better than a slow store. We\'ve worked with multiple caching solutions to write up guides on how to configure their plugin or services to work best with Easy Digital Downloads.', 'easy-digital-downloads' ), + 'image' => 'configuring-caching.svg', + ), + ); + + foreach ( $links as $link ) { + ?> +
    +
    + +
    +
    +

    + +

    + +

    + +

    + + + + +
    +
    + array( + 'icon' => $images_url . 'plugin-om.png', + 'name' => __( 'OptinMonster', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Instantly get more subscribers, leads, and sales with the #1 conversion optimization toolkit. Create high converting popups, announcement bars, spin a wheel, and more with smart targeting and personalization.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/optinmonster/', + 'url' => 'https://downloads.wordpress.org/plugin/optinmonster.zip', + ), + + 'google-analytics-for-wordpress/googleanalytics.php' => array( + 'icon' => $images_url . 'plugin-mi.png', + 'name' => __( 'MonsterInsights', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'The leading WordPress analytics plugin that shows you how people find and use your website, so you can make data driven decisions to grow your business. Properly set up Google Analytics without writing code.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/google-analytics-for-wordpress/', + 'url' => 'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip', + 'pro' => array( + 'plug' => 'google-analytics-premium/googleanalytics-premium.php', + 'name' => __( 'MonsterInsights Pro', 'easy-digital-downloads' ), + 'url' => 'https://www.monsterinsights.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'wp-mail-smtp/wp_mail_smtp.php' => array( + 'icon' => $images_url . 'plugin-smtp.png', + 'name' => __( 'WP Mail SMTP', 'easy-digital-downloads' ), + 'desc' => esc_html__( "Improve your WordPress email deliverability and make sure that your website emails reach user's inbox with the #1 SMTP plugin for WordPress. Over 3 million websites use it to fix WordPress email issues.", 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/wp-mail-smtp/', + 'url' => 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip', + 'pro' => array( + 'plug' => 'wp-mail-smtp-pro/wp_mail_smtp.php', + 'name' => __( 'WP Mail SMTP Pro', 'easy-digital-downloads' ), + 'url' => 'https://wpmailsmtp.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'all-in-one-seo-pack/all_in_one_seo_pack.php' => array( + 'icon' => $images_url . 'plugin-aioseo.png', + 'name' => __( 'AIOSEO', 'easy-digital-downloads' ), + 'desc' => esc_html__( "The original WordPress SEO plugin and toolkit that improves your website's search rankings. Comes with all the SEO features like Local SEO, WooCommerce SEO, sitemaps, SEO optimizer, schema, and more.", 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/all-in-one-seo-pack/', + 'url' => 'https://downloads.wordpress.org/plugin/all-in-one-seo-pack.zip', + 'pro' => array( + 'plug' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php', + 'name' => __( 'AIOSEO Pro', 'easy-digital-downloads' ), + 'url' => 'https://aioseo.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'coming-soon/coming-soon.php' => array( + 'icon' => $images_url . 'plugin-seedprod.png', + 'name' => __( 'SeedProd', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'The fastest drag & drop landing page builder for WordPress. Create custom landing pages without writing code, connect them with your CRM, collect subscribers, and grow your audience. Trusted by 1 million sites.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/coming-soon/', + 'url' => 'https://downloads.wordpress.org/plugin/coming-soon.zip', + 'pro' => array( + 'plug' => 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php', + 'name' => __( 'SeedProd Pro', 'easy-digital-downloads' ), + 'url' => 'https://www.seedprod.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'rafflepress/rafflepress.php' => array( + 'icon' => $images_url . 'plugin-rp.png', + 'name' => __( 'RafflePress', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Turn your website visitors into brand ambassadors! Easily grow your email list, website traffic, and social media followers with the most powerful giveaways & contests plugin for WordPress.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/rafflepress/', + 'url' => 'https://downloads.wordpress.org/plugin/rafflepress.zip', + 'pro' => array( + 'plug' => 'rafflepress-pro/rafflepress-pro.php', + 'icon' => $images_url . 'plugin-rp.png', + 'name' => __( 'RafflePress Pro', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Turn your website visitors into brand ambassadors! Easily grow your email list, website traffic, and social media followers with the most powerful giveaways & contests plugin for WordPress.', 'easy-digital-downloads' ), + 'url' => 'https://rafflepress.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'pushengage/main.php' => array( + 'icon' => $images_url . 'plugin-pushengage.png', + 'name' => __( 'PushEngage', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Connect with your visitors after they leave your website with the leading web push notification software. Over 10,000+ businesses worldwide use PushEngage to send 15 billion notifications each month.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/pushengage/', + 'url' => 'https://downloads.wordpress.org/plugin/pushengage.zip', + ), + + 'instagram-feed/instagram-feed.php' => array( + 'icon' => $images_url . 'plugin-sb-instagram.png', + 'name' => __( 'Smash Balloon Instagram Feeds', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Easily display Instagram content on your WordPress site without writing any code. Comes with multiple templates, ability to show content from multiple accounts, hashtags, and more. Trusted by 1 million websites.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/instagram-feed/', + 'url' => 'https://downloads.wordpress.org/plugin/instagram-feed.zip', + 'pro' => array( + 'plug' => 'instagram-feed-pro/instagram-feed.php', + 'name' => __( 'Smash Balloon Instagram Feeds Pro', 'easy-digital-downloads' ), + 'url' => 'https://smashballoon.com/instagram-feed/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'custom-facebook-feed/custom-facebook-feed.php' => array( + 'icon' => $images_url . 'plugin-sb-fb.png', + 'name' => __( 'Smash Balloon Facebook Feeds', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Easily display Facebook content on your WordPress site without writing any code. Comes with multiple templates, ability to embed albums, group content, reviews, live videos, comments, and reactions.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/custom-facebook-feed/', + 'url' => 'https://downloads.wordpress.org/plugin/custom-facebook-feed.zip', + 'pro' => array( + 'plug' => 'custom-facebook-feed-pro/custom-facebook-feed.php', + 'icon' => $images_url . 'plugin-sb-fb.png', + 'name' => __( 'Smash Balloon Facebook Feeds Pro', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Easily display Facebook content on your WordPress site without writing any code. Comes with multiple templates, ability to embed albums, group content, reviews, live videos, comments, and reactions.', 'easy-digital-downloads' ), + 'url' => 'https://smashballoon.com/custom-facebook-feed/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'feeds-for-youtube/youtube-feed.php' => array( + 'icon' => $images_url . 'plugin-sb-youtube.png', + 'name' => __( 'Smash Balloon YouTube Feeds', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Easily display YouTube videos on your WordPress site without writing any code. Comes with multiple layouts, ability to embed live streams, video filtering, ability to combine multiple channel videos, and more.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/feeds-for-youtube/', + 'url' => 'https://downloads.wordpress.org/plugin/feeds-for-youtube.zip', + 'pro' => array( + 'plug' => 'youtube-feed-pro/youtube-feed.php', + 'name' => __( 'Smash Balloon YouTube Feeds Pro', 'easy-digital-downloads' ), + 'url' => 'https://smashballoon.com/youtube-feed/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'custom-twitter-feeds/custom-twitter-feed.php' => array( + 'icon' => $images_url . 'plugin-sb-twitter.png', + 'name' => __( 'Smash Balloon Twitter Feeds', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Easily display Twitter content in WordPress without writing any code. Comes with multiple layouts, ability to combine multiple Twitter feeds, Twitter card support, tweet moderation, and more.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/custom-twitter-feeds/', + 'url' => 'https://downloads.wordpress.org/plugin/custom-twitter-feeds.zip', + 'pro' => array( + 'plug' => 'custom-twitter-feeds-pro/custom-twitter-feed.php', + 'name' => __( 'Smash Balloon Twitter Feeds Pro', 'easy-digital-downloads' ), + 'url' => 'https://smashballoon.com/custom-twitter-feeds/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'trustpulse-api/trustpulse.php' => array( + 'icon' => $images_url . 'plugin-trustpulse.png', + 'name' => __( 'TrustPulse', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Boost your sales and conversions by up to 15% with real-time social proof notifications. TrustPulse helps you show live user activity and purchases to help convince other users to purchase.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/trustpulse-api/', + 'url' => 'https://downloads.wordpress.org/plugin/trustpulse-api.zip', + ), + + 'searchwp/index.php' => array( + 'icon' => $images_url . 'plugin-searchwp.png', + 'name' => __( 'SearchWP', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'The most advanced WordPress search plugin. Customize your WordPress search algorithm, reorder search results, track search metrics, and everything you need to leverage search to grow your business.', 'easy-digital-downloads' ), + 'wporg' => false, + 'url' => 'https://searchwp.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + + 'affiliate-wp/affiliate-wp.php' => array( + 'icon' => $images_url . 'plugin-affwp.png', + 'name' => __( 'AffiliateWP', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'The #1 affiliate management plugin for WordPress. Easily create an affiliate program for your eCommerce store or membership site within minutes and start growing your sales with the power of referral marketing.', 'easy-digital-downloads' ), + 'wporg' => false, + 'url' => 'https://affiliatewp.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + + 'stripe/stripe-checkout.php' => array( + 'icon' => $images_url . 'plugin-wp-simple-pay.png', + 'name' => __( 'WP Simple Pay', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'The #1 Stripe payments plugin for WordPress. Start accepting one-time and recurring payments on your WordPress site without setting up a shopping cart. No code required.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/stripe/', + 'url' => 'https://downloads.wordpress.org/plugin/stripe.zip', + 'pro' => array( + 'plug' => 'wp-simple-pay-pro-3/simple-pay.php', + 'name' => __( 'WP Simple Pay Pro', 'easy-digital-downloads' ), + 'url' => 'https://wpsimplepay.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'wpforms-lite/wpforms.php' => array( + 'icon' => $images_url . 'plugin-wpf.png', + 'name' => __( 'WPForms', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'The best drag & drop WordPress form builder. Easily create beautiful contact forms, surveys, payment forms, and more with our 100+ form templates. Trusted by over 4 million websites as the best forms plugin.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/wpforms-lite/', + 'url' => 'https://downloads.wordpress.org/plugin/wpforms-lite.zip', + 'pro' => array( + 'plug' => 'wpforms/wpforms.php', + 'name' => __( 'WPForms Pro', 'easy-digital-downloads' ), + 'url' => 'https://wpforms.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'sugar-calendar-lite/sugar-calendar-lite.php' => array( + 'icon' => $images_url . 'plugin-sugarcalendar.png', + 'name' => __( 'Sugar Calendar', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'A simple & powerful event calendar plugin for WordPress that comes with all the event management features including payments, scheduling, timezones, ticketing, recurring events, and more.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/sugar-calendar-lite/', + 'url' => 'https://downloads.wordpress.org/plugin/sugar-calendar-lite.zip', + 'pro' => array( + 'plug' => 'sugar-calendar/sugar-calendar.php', + 'name' => __( 'Sugar Calendar Pro', 'easy-digital-downloads' ), + 'url' => 'https://sugarcalendar.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'charitable/charitable.php' => array( + 'icon' => $images_url . 'plugin-charitable.png', + 'name' => __( 'WP Charitable', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Top-rated WordPress donation and fundraising plugin. Over 10,000+ non-profit organizations and website owners use Charitable to create fundraising campaigns and raise more money online.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/charitable/', + 'url' => 'https://downloads.wordpress.org/plugin/charitable.zip', + ), + + 'insert-headers-and-footers/ihaf.php' => array( + 'icon' => $images_url . 'plugin-wpcode.png', + 'name' => __( 'WPCode', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Future proof your WordPress customizations with the most popular code snippet management plugin for WordPress. Trusted by over 1,500,000+ websites for easily adding code to WordPress right from the admin area.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/insert-headers-and-footers/', + 'url' => 'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip', + 'pro' => array( + 'plug' => 'wpcode-premium/wpcode.php', + 'name' => __( 'WPCode Pro', 'easy-digital-downloads' ), + 'url' => 'https://wpcode.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + + 'duplicator/duplicator.php' => array( + 'icon' => $images_url . 'plugin-duplicator.png', + 'name' => __( 'Duplicator', 'easy-digital-downloads' ), + 'desc' => esc_html__( 'Leading WordPress backup & site migration plugin. Over 1,500,000+ smart website owners use Duplicator to make reliable and secure WordPress backups to protect their websites. It also makes website migration really easy.', 'easy-digital-downloads' ), + 'wporg' => 'https://wordpress.org/plugins/duplicator/', + 'url' => 'https://downloads.wordpress.org/plugin/duplicator.zip', + 'pro' => array( + 'plug' => 'duplicator-pro/duplicator-pro.php', + 'name' => __( 'Duplicator Pro', 'easy-digital-downloads' ), + 'url' => 'https://duplicator.com/?utm_source=eddplugin&utm_medium=link&utm_campaign=About%20EDD', + 'act' => 'go-to-url', + ), + ), + ); + } + + /** + * Get AM plugin data to display in the Addons section of About tab. + * + * @since 3.2.4 + * + * @param string $plugin Plugin slug. + * @param array $details Plugin details. + * @param array $all_plugins List of all plugins. + * + * @return array + */ + private function get_plugin_data( $plugin, $details, $all_plugins ) { + + $have_pro = ( ! empty( $details['pro'] ) && ! empty( $details['pro']['plug'] ) ); + $show_pro = false; + + $plugin_data = array(); + + if ( $have_pro ) { + if ( array_key_exists( $plugin, $all_plugins ) ) { + if ( is_plugin_active( $plugin ) ) { + $show_pro = true; + } + } + + if ( array_key_exists( $details['pro']['plug'], $all_plugins ) ) { + $show_pro = true; + } + + if ( $show_pro ) { + // Pull out the pro plugin details, and remove it from the array. + $pro_details = $details['pro']; + unset( $details['pro'] ); + + // Now merge the pro details with the main details. + $details = array_merge( $details, $pro_details ); + + // Now unset the 'worg' url. + unset( $details['worg'] ); + + // Set the plugin slug as the pro plugin. + $plugin = $details['plug']; + } + } + + if ( array_key_exists( $plugin, $all_plugins ) ) { + if ( is_plugin_active( $plugin ) ) { + $plugin_data['status_text'] = esc_html__( 'Active', 'easy-digital-downloads' ); + $plugin_data['button_text'] = esc_html__( 'Installed & Active', 'easy-digital-downloads' ); + $plugin_data['plugin_status'] = 'active'; + $plugin_data['plugin'] = esc_attr( $plugin ); + $plugin_data['action'] = false; + } else { + $plugin_data['status_text'] = esc_html__( 'Inactive', 'easy-digital-downloads' ); + $plugin_data['button_text'] = esc_html__( 'Activate', 'easy-digital-downloads' ); + $plugin_data['plugin_status'] = 'inactive'; + $plugin_data['action'] = 'activate'; + $plugin_data['plugin'] = esc_attr( $plugin ); + } + } else { + $plugin_data['status_text'] = esc_html__( 'Not Installed', 'easy-digital-downloads' ); + $plugin_data['button_text'] = esc_html__( 'Install Plugin', 'easy-digital-downloads' ); + $plugin_data['plugin_status'] = 'not-installed'; + $plugin_data['action'] = 'install'; + $plugin_data['plugin'] = esc_url( $details['url'] ); + + if ( isset( $details['act'] ) && 'go-to-url' === $details['act'] ) { + $plugin_data['action'] = 'goto'; + $plugin_data['plugin'] = esc_url( $details['url'] ); + } + } + + $plugin_data['details'] = $details; + $plugin_data['button_parameters'] = array( + 'plugin' => $plugin_data['plugin'], + 'action' => $plugin_data['action'], + 'button_text' => $plugin_data['button_text'], + ); + + return $plugin_data; + } +} diff --git a/src/Admin/Promos/Footer/FlyoutMenu.php b/src/Admin/Promos/Footer/FlyoutMenu.php new file mode 100644 index 00000000000..a59c741cd5c --- /dev/null +++ b/src/Admin/Promos/Footer/FlyoutMenu.php @@ -0,0 +1,190 @@ + +
    + %1$s +
    + + ', + self::get_items_html(), // phpcs:ignore + edd_is_inactive_pro() ? 'has-alert' : '', + esc_attr__( 'See Quick Links', 'easy-digital-downloads' ), + esc_url( EDD_PLUGIN_URL . 'assets/images/admin-flyout-menu/edd-default.svg' ), + esc_url( EDD_PLUGIN_URL . 'assets/images/admin-flyout-menu/edd-active.svg' ), + ); + } + + /** + * Generate menu items HTML. + * + * @since 3.2.4 + * + * @return string Menu items HTML. + */ + private static function get_items_html() { + $items = array_reverse( self::menu_items() ); + $items_html = ''; + + foreach ( $items as $item_key => $item ) { + $items_html .= sprintf( + ' + %5$s + + ', + \esc_url( $item['url'] ), + $item['is_external'] ? ' target="_blank" rel="noopener noreferrer" ' : ' ', + \esc_attr( $item_key ), + ! empty( $item['class'] ) ? ' ' . \sanitize_html_class( $item['class'] ) : '', + \esc_html( $item['title'] ), + \sanitize_html_class( $item['icon'] ) + ); + } + + return $items_html; + } + + /** + * Menu items data. + * + * @since 3.2.4 + */ + private static function menu_items() { + $items = array(); + $screen = get_current_screen(); + $screen = ! empty( $screen ) ? $screen->id : 'unknown'; + $screen = str_replace( 'download_page_edd-', '', $screen ); + + // Set the UTM parameters for this menu set. + $utm_medium = 'admin-flyout'; + $utm_term = str_replace( '_', '-', $screen ); + + $pass_manager = new \EDD\Admin\Pass_Manager(); + if ( ! edd_is_pro() && ! $pass_manager->has_pass() ) { + $items['upgrade'] = array( + 'title' => esc_html__( 'Upgrade to EDD (Pro)', 'easy-digital-downloads' ), + 'url' => edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => $utm_medium, + 'utm_content' => 'upgrade-to-pro', + 'utm_term' => $utm_term, + ) + ), + 'icon' => 'star-filled', + 'class' => 'green', + 'is_external' => true, + ); + } + + if ( edd_is_inactive_pro() ) { + $items['activate'] = array( + 'title' => esc_html__( 'Activate Your License', 'easy-digital-downloads' ), + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-settings', + ) + ), + 'icon' => 'unlock', + 'class' => 'red', + 'is_external' => false, + ); + + $items['license'] = array( + 'title' => esc_html__( 'Get a License', 'easy-digital-downloads' ), + 'url' => edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => $utm_medium, + 'utm_content' => '', + 'utm_term' => $utm_term, + ) + ), + 'icon' => 'star-filled', + 'class' => 'green', + 'hover_bgcolor' => '#199155', + 'is_external' => true, + ); + } + + $support_url = ! edd_is_pro() || edd_is_inactive_pro() + ? 'https://wordpress.org/support/plugin/easy-digital-downloads/' + : edd_link_helper( + 'https://easydigitaldownloads.com/support/', + array( + 'utm_medium' => $utm_medium, + 'utm_content' => 'support', + 'utm_term' => $utm_term, + ) + ); + + $items['support'] = array( + 'title' => esc_html__( 'Contact Support', 'easy-digital-downloads' ), + 'url' => $support_url, + 'icon' => 'sos', + 'is_external' => true, + ); + + $items['docs'] = array( + 'title' => esc_html__( 'View Documentation', 'easy-digital-downloads' ), + 'url' => edd_link_helper( + apply_filters( 'edd_flyout_docs_link', 'https://easydigitaldownloads.com/docs/' ), + array( + 'utm_medium' => $utm_medium, + 'utm_content' => 'docs', + 'utm_term' => $utm_term, + ) + ), + 'icon' => 'text-page', + 'is_external' => true, + ); + + return apply_filters( 'edd_admin_flyout_menu_items', $items ); + } + + /** + * Enqueue scripts. + * + * @since 3.2.4 + */ + private static function enqueue() { + wp_enqueue_script( + 'edd-flyout-menu', + EDD_PLUGIN_URL . 'assets/js/edd-admin-flyout.js', + array(), + EDD_VERSION, + true + ); + } +} diff --git a/src/Admin/Promos/Footer/Links.php b/src/Admin/Promos/Footer/Links.php new file mode 100644 index 00000000000..242e779d883 --- /dev/null +++ b/src/Admin/Promos/Footer/Links.php @@ -0,0 +1,138 @@ + edd_is_pro() ? + edd_link_helper( + 'https://easydigitaldownloads.com/support/', + array( + 'utm_medium' => 'admin-footer', + 'utm_content' => 'support', + ), + ) : 'https://wordpress.org/support/plugin/easy-digital-downloads/', + 'text' => __( 'Support', 'easy-digital-downloads' ), + 'target' => '_blank', + ), + array( + 'url' => edd_link_helper( + 'https://easydigitaldownloads.com/docs/', + array( + 'utm_medium' => 'admin-footer', + 'utm_content' => 'docs', + ), + ), + 'text' => __( 'Docs', 'easy-digital-downloads' ), + 'target' => '_blank', + ), + ); + + $screen = get_current_screen(); + if ( 'download_page_edd-about' !== $screen->id ) { + $footer_links[] = array( + 'url' => admin_url( 'edit.php?post_type=download&page=edd-about' ), + 'text' => __( 'Free Plugins', 'easy-digital-downloads' ), + ); + } + + $links_count = count( $footer_links ); + ?> + + + 'add_hooks_and_filters', + ); + } + + /** + * Adds the hooks for our footer content. + * + * @since 3.2.4 + */ + public function add_hooks_and_filters() { + // Bail if we're not on an EDD admin page. + if ( ! edd_is_admin_page( '', '', false ) ) { + return; + } + + // Get the hooks that are allowed to run on this admin page. + $allowed_hooks = $this->get_allowed_hooks(); + + // If no hooks are returned, bail. + if ( empty( $allowed_hooks ) ) { + return; + } + + foreach ( $allowed_hooks as $allowed_hook ) { + $hook = $allowed_hook['hook']; + $type = $allowed_hook['type']; + $target = $allowed_hook['target']; + $priority = $allowed_hook['priority']; + $args = isset( $allowed_hook['args'] ) ? $allowed_hook['args'] : 0; + + if ( 'action' === $type ) { + add_action( $hook, $target, $priority, $args ); + } else { + add_filter( $hook, $target, $priority, $args ); + } + } + } + + /** + * Get the allowed hooks for the current admin page. + * + * @since 3.2.4 + * + * @return array + */ + private function get_allowed_hooks() { + $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false; + + // If we can't figure out what screen we're on, just avoid loading any hooks. + if ( empty( $current_screen ) ) { + return array(); + } + + // We don't want to load any of these on the edit or add download screens. + $post = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : false; + $action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : false; + + $is_edit_screen = $post && 'edit' === $action; + if ( 'add' === $current_screen->action || $is_edit_screen ) { + return array(); + } + + // If this is the dashboard, we don't want to load our footer contents either. + if ( 'dashboard' === $current_screen->id ) { + return array(); + } + + $registered_classes = $this->get_classes(); + $page_exclusions = $this->page_exclusions(); + if ( ! array_key_exists( $current_screen->id, $page_exclusions ) ) { + return $registered_classes; + } + + foreach ( $registered_classes as $key => $data ) { + if ( in_array( $key, $page_exclusions[ $current_screen->id ], true ) ) { + unset( $registered_classes[ $key ] ); + } + } + + return $registered_classes; + } + + /** + * Get the conditions for the footer content. + * + * @since 3.2.4 + * + * @return array + */ + private function get_classes() { + $classes = array( + 'links' => array( + 'hook' => 'in_admin_footer', + 'type' => 'action', + 'target' => array( 'EDD\Admin\Promos\Footer\Links', 'footer_content' ), + 'priority' => 99, + ), + 'review' => array( + 'hook' => 'admin_footer_text', + 'type' => 'filter', + 'target' => array( 'EDD\Admin\Promos\Footer\Review', 'review_message' ), + 'priority' => 99, + 'args' => 1, + ), + 'version' => array( + 'hook' => 'update_footer', + 'type' => 'filter', + 'target' => array( 'EDD\Admin\Promos\Footer\Version', 'version_message' ), + 'priority' => 99, + 'args' => 1, + ), + 'flyout' => array( + 'hook' => 'admin_footer', + 'type' => 'action', + 'target' => array( 'EDD\Admin\Promos\Footer\FlyoutMenu', 'output' ), + 'priority' => 99, + ), + ); + + return $classes; + } + + /** + * Get the admin pages that should not load specific footer content. + * + * @since 3.2.4 + * + * @return array + */ + private function page_exclusions() { + return apply_filters( + 'edd_admin_page_footer_exclusions', + array( + 'download_page_edd-onboarding-wizard' => array( + 'flyout', + 'version', + 'review', + 'links', + ), + ) + ); + } +} diff --git a/src/Admin/Promos/Footer/Review.php b/src/Admin/Promos/Footer/Review.php new file mode 100644 index 00000000000..32d35e1e5d3 --- /dev/null +++ b/src/Admin/Promos/Footer/Review.php @@ -0,0 +1,46 @@ + array( + 'href' => array(), + 'target' => array(), + 'rel' => array(), + ), + ) + ), + 'Easy Digital Downloads', + '★★★★★', + 'WordPress.org', + ); + + return $text; + } +} diff --git a/src/Admin/Promos/Footer/Version.php b/src/Admin/Promos/Footer/Version.php new file mode 100644 index 00000000000..310af4b2066 --- /dev/null +++ b/src/Admin/Promos/Footer/Version.php @@ -0,0 +1,34 @@ + +

    +

    + +

    +
    + do_cards(); ?> +
    +
    + do_action_buttons(); + ?> +
    + _should_display() ) { + return ''; + } + $product_id = filter_input( INPUT_GET, 'product_id', FILTER_SANITIZE_NUMBER_INT ); + if ( empty( $product_id ) ) { + return ''; + } + ob_start(); + ?> +

    +

    + +

    +
    + get_extensions_class(); + $extensions->do_single_extension_card( absint( $product_id ) ); + ?> +
    + 'edd-addons', + 'filter' => 'email-management', + ) + ); + ?> + + + + extensions ) ) { + $email_class = edd_get_namespace( 'Admin\\Extensions\\Emails' ); + $this->extensions = new $email_class(); + } + + return $this->extensions; + } + + /** + * Outputs the extension cards. + * + * @since 3.3.0 + */ + protected function do_cards() { + $extensions = $this->get_extensions_class(); + foreach ( $extensions->get_product_data() as $item_id => $product ) { + $extensions->do_single_extension_card( $item_id ); + } + } +} diff --git a/src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php b/src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php new file mode 100644 index 00000000000..8578c255930 --- /dev/null +++ b/src/Admin/Promos/Notices/Five_Star_Review_Dashboard.php @@ -0,0 +1,200 @@ + +
    + _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/src/Admin/Promos/Notices/Five_Star_Review_Settings.php b/src/Admin/Promos/Notices/Five_Star_Review_Settings.php new file mode 100644 index 00000000000..50dc02c15a3 --- /dev/null +++ b/src/Admin/Promos/Notices/Five_Star_Review_Settings.php @@ -0,0 +1,75 @@ + +
    + +
    + 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(); + $this->dismiss_button(); + ?> +
    + + + _should_display() ) { + return ''; + } + + ob_start(); + ?> +

    + +

    + +
    + + +
    + do_single_extension_card( 28530 ); + } + + /** + * Duration (in seconds) that the notice is dismissed for. + * `0` means it's dismissed permanently. + * + * @return int + */ + public static function dismiss_duration() { + return 1; + } + + /** + * @inheritDoc + * @since 3.3.5 + * @return bool + */ + protected function _should_display() { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return false; + } + + $tab = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_SPECIAL_CHARS ); + $section = filter_input( INPUT_GET, 'section', FILTER_SANITIZE_SPECIAL_CHARS ); + + return 'gateways' === $tab && 'edd-stripe' === $section; + } +} diff --git a/src/Admin/Promos/PromoHandler.php b/src/Admin/Promos/PromoHandler.php new file mode 100644 index 00000000000..b034f5e3e5b --- /dev/null +++ b/src/Admin/Promos/PromoHandler.php @@ -0,0 +1,240 @@ +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', + 'wp_ajax_edd_get_promo_notice' => 'get_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 ( FileSystem::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(); + } + + /** + * AJAX callback for getting a notice. + * + * @since 3.3.0 + */ + public function get_notice() { + $notice_id = filter_input( INPUT_GET, 'notice_id', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( empty( $notice_id ) ) { + wp_send_json_error( __( 'Missing notice ID.', 'easy-digital-downloads' ), 400 ); + } + $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 ); + } + + $notice = new $notice_class_name(); + $content = $notice->get_ajax_content(); + if ( $content ) { + ob_start(); + $notice->dismiss_button(); + $content .= ob_get_clean(); + } + + wp_send_json_success( $content ); + } + + /** + * 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->get_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 ( FileSystem::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/Sections.php b/src/Admin/Sections.php new file mode 100644 index 00000000000..bdf4f56b871 --- /dev/null +++ b/src/Admin/Sections.php @@ -0,0 +1,343 @@ +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 The item to set. + */ + 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 The section ID to check. + * + * @return bool + */ + protected 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 ) : + + // If were using JS, the URL is a hash, otherwise use query args. + $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"' + : ''; + + $classes = array( + 'section-title', + "section-title--{$section->id}", + ); + if ( $this->is_current_section( $section->id ) ) { + $classes[] = 'section-title--is-active'; + } + $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 ''; + } + + ob_start(); + + // Loop through sections. + foreach ( $sections as $section ) : + + // Special selected section. + $selected = ! $this->is_current_section( $section->id ) + ? 'style="display: none;"' + : ''; + + $classes = $section->classes ?? array( 'section-content' ); + ?> + +
    > + callback ) ) { + if ( is_callable( $section->callback ) ) { + call_user_func( $section->callback, $this->item ); + } elseif ( is_array( $section->callback ) && is_callable( $section->callback[0] ) ) { + $parameters = array(); + if ( isset( $section->callback[1] ) ) { + $parameters = $section->callback[1]; + } + call_user_func_array( $section->callback[0], $parameters ); + } else { + esc_html_e( 'Invalid section', 'easy-digital-downloads' ); + } + } else { + die; + do_action( 'edd_' . $section->id . 'section_contents', $this ); + } + + ?> +
    + + + + + + + + + + + +
    + + + — +
    + + id = $id; + } + } + + /** + * Section ID. + * + * @since 3.3.6 + * @var string + */ + protected $id; + + /** + * Section priority. + * + * @since 3.3.6 + * @var int + */ + protected $priority = 10; + + /** + * Section icon. + * + * @since 3.3.6 + * @var string + */ + protected $icon = ''; + + /** + * Get the section priority. + * + * @since 3.3.6 + * @return int + */ + public function get_priority() { + return $this->priority; + } + + /** + * Set the item. + * + * @since 3.3.6 + * @param object $item The item to set. + * @return void + */ + public function set_item( $item ) { + $this->item = $item; + } + + /** + * Get the section label. + * + * @since 3.3.6 + * @return string + */ + abstract public function get_label(); + + /** + * Get the section config. + * + * @since 3.3.6 + * @return array + */ + public function get_config() { + return array( + 'id' => $this->get_id(), + 'label' => $this->get_label(), + 'callback' => array( $this, 'render' ), + 'icon' => $this->icon, + 'priority' => $this->priority, + 'classes' => $this->get_classes(), + ); + } + + /** + * Render the section. + * + * @since 3.3.6 + * @return void + */ + abstract public function render(); + + /** + * Get the section ID. + * + * @since 3.3.6 + * @return string + */ + protected function get_id() { + return $this->id; + } + + /** + * Get the section classes. + * + * @since 3.3.6 + * @return array + */ + protected function get_classes(): array { + return array( + 'section-content', + "section-content--{$this->get_id()}", + ); + } +} 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..ddaccc21ca0 --- /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', 'easy-digital-downloads' ), + 'description' => $this->get_custom_description(), + 'features' => array( + __( 'Generate Attractive Invoices', 'easy-digital-downloads' ), + __( 'Build Customer Confidence', 'easy-digital-downloads' ), + __( 'PDF Download Support', 'easy-digital-downloads' ), + __( 'Include in Purchase Emails', 'easy-digital-downloads' ), + __( 'Customizable Templates', 'easy-digital-downloads' ), + ), + ); + } + + /** + * 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.', 'easy-digital-downloads' ), + __( 'Provide a professional experience with customizable templates and one-click PDF downloads. ', 'easy-digital-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/Pointers.php b/src/Admin/Settings/Pointers.php new file mode 100644 index 00000000000..0cc7371f520 --- /dev/null +++ b/src/Admin/Settings/Pointers.php @@ -0,0 +1,65 @@ + 'edd_base_country_input', + 'target' => '#edd_settings_base_country__chosen', + 'options' => array( + 'content' => sprintf( + '

    %s

    %s

    ', + __( 'Update your Store\'s Country', 'easy-digital-downloads' ), + __( 'Your store does not have the Business Country set. Easy Digital Downloads uses this setting to properly configure settings for your store. Please update your country to get the best experience.', 'easy-digital-downloads' ) + ), + 'position' => array( + 'edge' => 'left', + 'align' => 'center', + ), + 'pointerClass' => 'edd-pointer warning', + ), + ); + } + + return $pointers; + } +} diff --git a/src/Admin/Settings/Recurring.php b/src/Admin/Settings/Recurring.php new file mode 100644 index 00000000000..5b71f832348 --- /dev/null +++ b/src/Admin/Settings/Recurring.php @@ -0,0 +1,139 @@ + 'add_section', + 'edd_settings_tab_top_gateways_recurring' => 'settings_field', + 'edd_settings_tab_bottom_gateways_recurring' => 'hide_submit_button', + 'edd_settings_wrap_classes' => 'add_wrap_class', + ); + } + + /** + * 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!', 'easy-digital-downloads' ), + 'description' => $this->get_custom_description(), + 'features' => array( + __( 'Flexible Recurring Payments', 'easy-digital-downloads' ), + __( 'Custom Reminder Emails', 'easy-digital-downloads' ), + __( 'Free Trial Support', 'easy-digital-downloads' ), + __( 'Signup Fees', 'easy-digital-downloads' ), + __( 'Recurring Revenue Reports', 'easy-digital-downloads' ), + ), + ); + } + + /** + * 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.', 'easy-digital-downloads' ), + __( 'When your customers are automatically billed, you reduce the risk of missed payments and retain more customers.', 'easy-digital-downloads' ), + ); + + return $this->format_description( $description ); + } + + /** + * Adds the Recurring Payments section to the settings. + * + * @param array $sections The existing 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..13397a8d0bd --- /dev/null +++ b/src/Admin/Settings/Reviews.php @@ -0,0 +1,158 @@ + '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', + 'edd_settings_wrap_classes' => 'add_wrap_class', + ); + } + + /** + * 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', 'easy-digital-downloads' ), + ); + $settings_configuration = array( + 'style' => 'detailed-2col', + 'description' => $this->get_custom_description(), + 'features' => array( + __( 'Request Reviews', 'easy-digital-downloads' ), + __( 'Incentivize Reviewers', 'easy-digital-downloads' ), + __( 'Full Schema.org Support', 'easy-digital-downloads' ), + __( 'Embed Reviews Via Blocks', 'easy-digital-downloads' ), + __( 'Limit Reviews to Customers', 'easy-digital-downloads' ), + __( 'Vendor Reviews (with Frontend Submissions)', 'easy-digital-downloads' ), + ), + ); + 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.', 'easy-digital-downloads' ), + __( 'Easily collect, manage, and beautifully display reviews all from your WordPress dashboard.', 'easy-digital-downloads' ), + ); + + 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..75fd88d0e39 --- /dev/null +++ b/src/Admin/Settings/Sanitize.php @@ -0,0 +1,31 @@ + 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/Sanitize/Tabs/Gateways/Checkout.php b/src/Admin/Settings/Sanitize/Tabs/Gateways/Checkout.php new file mode 100644 index 00000000000..ef87d0061da --- /dev/null +++ b/src/Admin/Settings/Sanitize/Tabs/Gateways/Checkout.php @@ -0,0 +1,49 @@ + $email ) { + if ( ! is_email( $email ) && '@' !== $email[0] && '.' !== $email[0] ) { + unset( $emails[ $id ] ); + } + } + + // Before return, make sure the array is re-indexed. + $emails = array_values( $emails ); + } + + return $emails; + } +} diff --git a/src/Admin/Settings/Sanitize/Tabs/Gateways/EddStripe.php b/src/Admin/Settings/Sanitize/Tabs/Gateways/EddStripe.php new file mode 100644 index 00000000000..dfb710a97e1 --- /dev/null +++ b/src/Admin/Settings/Sanitize/Tabs/Gateways/EddStripe.php @@ -0,0 +1,138 @@ + $label ) { + if ( ! isset( $configuration->$method ) ) { + continue; + } + $methods[ $method ] = $configuration->$method; + } + + self::update_configuration( $methods, $input, $configuration_id ); + } catch ( \Exception $e ) { + edd_debug_log( $e->getMessage(), true ); + continue; + } + } + } + + /** + * Update the configuration. + * + * @since 3.3.5 + * @param object $configuration The configuration object. + * @param array $input The input array. + * @param string $configuration_id The configuration ID. + * @return bool|string False if no changes were made, otherwise the configuration ID. + */ + private static function update_configuration( $configuration, $input, $configuration_id = null ) { + $args = array(); + foreach ( $input as $method => $enabled ) { + if ( ! isset( $configuration[ $method ] ) || 'card' === $method ) { + continue; + } + if ( empty( $configuration[ $method ]['display_preference']['overridable'] ) ) { + continue; + } + $new_value = $enabled ? 'on' : 'off'; + if ( $new_value === $configuration[ $method ]['display_preference']['preference'] ) { + continue; + } + $args[ $method ]['display_preference']['preference'] = $new_value; + } + + if ( empty( $args ) ) { + return false; + } + + if ( ! $configuration_id ) { + $configuration_id = \EDD\Gateways\Stripe\PaymentMethods::get_configuration_id(); + } + + try { + edds_api_request( + 'PaymentMethodConfiguration', + 'update', + $configuration_id, + $args + ); + } catch ( \Exception $e ) { + edd_debug_log( $e->getMessage(), true ); + return false; + } + + return $configuration_id; + } +} diff --git a/src/Admin/Settings/Sanitize/Tabs/Gateways/Main.php b/src/Admin/Settings/Sanitize/Tabs/Gateways/Main.php new file mode 100644 index 00000000000..07e85932e33 --- /dev/null +++ b/src/Admin/Settings/Sanitize/Tabs/Gateways/Main.php @@ -0,0 +1,53 @@ + $value ) { + // If there is a private method for a key, use it to sanitize the value. + $method = 'sanitize_' . $key; + if ( method_exists( $section_class, $method ) ) { + $input[ $key ] = $section_class::$method( $value ); + } + } + + // Handle any additional processing for the section. + if ( method_exists( $section_class, 'additional_processing' ) ) { + $processed_input = $section_class::additional_processing( $input ); + + $input = is_array( $processed_input ) + ? $processed_input + : $input; + } + + return $input; + } +} diff --git a/src/Admin/Settings/Sanitize/Tabs/Tab.php b/src/Admin/Settings/Sanitize/Tabs/Tab.php new file mode 100644 index 00000000000..b735403fc3a --- /dev/null +++ b/src/Admin/Settings/Sanitize/Tabs/Tab.php @@ -0,0 +1,45 @@ + $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; + } +} diff --git a/src/Admin/Settings/Screen.php b/src/Admin/Settings/Screen.php new file mode 100644 index 00000000000..2d49d6b8392 --- /dev/null +++ b/src/Admin/Settings/Screen.php @@ -0,0 +1,372 @@ + $section_title ) { + if ( ! empty( $all_settings[ $active_tab ][ $section_key ] ) ) { + $section = $section_key; + $override = true; + break; + } + } + } + } + + // Primary nav. + self::primary_navigation( self::get_tabs(), $active_tab ); + // Secondary nav. + self::secondary_navigation( $active_tab, $section, self::$sections ); + ?> +
    + +
    + + +
    +
    +
    + + + +
    +
    +
    + render(); + } + + /** + * Output the secondary options page navigation + * + * @since 3.3.0 + * + * @param string $active_tab The active tab. + * @param string $section The active section. + * @param array $sections The available sections. + */ + public static function secondary_navigation( $active_tab = '', $section = '', $sections = array() ) { + + // Back compat for section'less tabs (Licenses, etc...). + if ( empty( $sections ) ) { + $section = 'main'; + $sections = array( + 'main' => __( 'General', 'easy-digital-downloads' ), + ); + } + + if ( count( $sections ) < 2 && 'main' === $section ) { + return; + } + + ?> +
    +
      + $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' + : ''; + + printf( + '
    • %3$s
    • ', + esc_attr( $class ), + esc_url( $tab_url ), + esc_html( $section_name ) + ); + } + ?> +
    +
    + $stitle ) { + if ( is_string( $sid ) && ! empty( $sections ) && array_key_exists( $sid, $sections ) ) { + continue; + } + $has_main = true; + break; + } + + return $has_main; + } +} diff --git a/src/Admin/Settings/Tabs/Emails.php b/src/Admin/Settings/Tabs/Emails.php new file mode 100644 index 00000000000..f49849982c4 --- /dev/null +++ b/src/Admin/Settings/Tabs/Emails.php @@ -0,0 +1,275 @@ + array( + 'email_template' => array( + 'id' => 'email_template', + 'name' => __( 'Template', 'easy-digital-downloads' ), + 'desc' => __( 'Choose a template. Once you\'ve saved your changes, preview an email 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(), + ), + 'admin_notice_emails' => array( + 'id' => 'admin_notice_emails', + 'name' => __( 'Admin Email Recipients', 'easy-digital-downloads' ), + 'desc' => sprintf( + /* translators: %s: admin email */ + __( 'Enter the email address(es) that may receive admin notices. One per line. Leave blank to use %s.', 'easy-digital-downloads' ), + '' . $this->get_admin_email() . '' + ), + 'type' => 'textarea', + 'std' => $this->get_admin_email(), + ), + 'email_settings' => array( + 'id' => 'email_settings', + 'name' => '', + 'desc' => '', + 'type' => 'hook', + ), + ), + 'purchase_receipts' => array( + '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', + ), + 'disable_order_receipt' => array( + 'id' => 'disable_order_receipt', + 'name' => __( 'Disable Order Receipt', 'easy-digital-downloads' ), + 'check' => __( 'Do not send purchase receipt emails to customers through Easy Digital Downloads.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ), + ), + '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', + ), + '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() { + return array( + 'email_summary_frequency' => array( + 'id' => 'email_summary_frequency', + 'name' => __( 'Email Frequency', 'easy-digital-downloads' ), + 'type' => 'select', + 'std' => 'weekly', + 'desc' => $this->get_email_summary_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' => $this->get_custom_recipient_class(), + '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' => __( 'Send Report Summaries', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + 'check' => __( 'Enable the summary emails.', 'easy-digital-downloads' ), + 'options' => array( + 'inverse' => true, + ), + ), + ); + } + + /** + * Retrieves the email summary text for the settings screen. + * + * @since 3.3.3 + * @return string The email summary text. + */ + private function get_email_summary_text() { + if ( ! $this->is_admin_page( 'emails' ) ) { + return ''; + } + + if ( 'email_summaries' !== $this->get_tab() ) { + return ''; + } + + if ( ! class_exists( '\\EDD\\Utils\\Date' ) ) { + return ''; + } + + $schedule = SingleEvent::next_scheduled( EmailSummaries::CRON_EVENT_NAME ); + if ( ! $schedule ) { + return ' ' . esc_html( __( 'The summary email is not yet scheduled. Save the settings to manually schedule it.', 'easy-digital-downloads' ) ) . ''; + } + + $date = \EDD\Utils\Date::createFromTimestamp( $schedule )->setTimezone( edd_get_timezone_id() ); + + return sprintf( + /* translators: formatted date */ + __( 'The next summary email is scheduled to send on %s.', 'easy-digital-downloads' ), + $date->format( get_option( 'date_format' ) ) + ); + } + + /** + * Retrieves the custom recipient class. + * + * @since 3.3.3 + * @return string The custom recipient class. + */ + private function get_custom_recipient_class() { + $email_summary_recipient = edd_get_option( 'email_summary_recipient', 'admin' ); + + return 'admin' === $email_summary_recipient ? 'hidden' : ''; + } + + /** + * Retrieves the trigger URL for the Emails tab. + * + * @since 3.3.3 + * @return string The trigger URL. + */ + private function get_trigger_url() { + return 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' + ); + } +} diff --git a/src/Admin/Settings/Tabs/Extensions.php b/src/Admin/Settings/Tabs/Extensions.php new file mode 100644 index 00000000000..93e1f76071d --- /dev/null +++ b/src/Admin/Settings/Tabs/Extensions.php @@ -0,0 +1,41 @@ + 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' => __( 'Redirect all customers to the secure checkout page. You must have an SSL certificate installed to use this option.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + 'options' => array( + 'disabled' => is_ssl() ? false : true, + ), + ), + 'redirect_on_add' => array( + 'id' => 'redirect_on_add', + 'name' => __( 'Redirect to Checkout', 'easy-digital-downloads' ), + 'check' => __( 'Immediately redirect to checkout after adding an item to the cart?', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + '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' => __( 'Customer Registration', 'easy-digital-downloads' ), + 'type' => 'select', + 'desc' => __( 'You may allow customers to place orders without a user account.', 'easy-digital-downloads' ) . + '
    ' . + __( 'Setting this to auto will create a user account if one does not exist for a customer.', 'easy-digital-downloads' ), + 'options' => array( + '' => __( 'Allow customers to place orders without an account', 'easy-digital-downloads' ), + 'required' => __( 'Customers must log in or create an account to purchase', 'easy-digital-downloads' ), + 'auto' => __( 'Automatically register new user accounts', 'easy-digital-downloads' ), + ), + ), + 'show_register_form' => $this->get_register_form(), + 'enable_cart_saving' => array( + 'id' => 'enable_cart_saving', + 'name' => __( 'Enable Cart Saving', 'easy-digital-downloads' ), + 'check' => __( 'Allow users to temporarily save their cart at checkout.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + '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' ), + ), + 'geolocation' => array( + 'id' => 'geolocation', + 'name' => __( 'Geolocation Detection', 'easy-digital-downloads' ), + 'desc' => $this->get_geolocation_description(), + 'type' => 'select', + 'options' => array( + '' => __( 'Disabled', 'easy-digital-downloads' ), + ), + 'disabled' => true, + ), + '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' => __( 'Enable Test Mode', 'easy-digital-downloads' ), + 'tooltip_title' => __( 'What is Test Mode?', 'easy-digital-downloads' ), + 'tooltip_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 your checkout process.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + ); + // 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' => __( 'SKUs will be shown on purchase receipt and exported purchase histories.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + ), + 'enable_sequential' => array( + 'id' => 'enable_sequential', + 'name' => __( 'Enable Sequential Numbering', 'easy-digital-downloads' ), + 'tooltip_title' => __( 'Sequential Order Numbers', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'This setting will not impact previous orders. Future orders will be assigned a sequential number.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + ), + '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: opening anchor tag, 2: closing anchor tag */ + __( 'Access %1$sAdvanced Sequential Order Numbers%2$s today.', 'easy-digital-downloads' ), + '', + '' + ); + } + + return $text; + } + + /** + * Get the geolocation description. + * + * @since 3.2.8 + * @return string + */ + private function get_geolocation_description() { + if ( ! empty( $this->get_pass_id() ) ) { + $settings_url = add_query_arg( + array( + 'page' => 'edd-settings', + ), + edd_get_admin_url() + ); + + return sprintf( + /* translators: 1: opening anchor tag, 2: closing anchor tag */ + __( 'GeoLocation Detection is only available in Easy Digital Downloads Pro. %1$sVerify your pass to get access to pro features.%2$s', 'easy-digital-downloads' ), + '', + '' + ); + } + + $upgrade_link = edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade', + array( + 'utm_medium' => 'settings', + 'utm-content' => 'geolocation', + ) + ); + + return sprintf( + /* translators: 1: opening anchor tag, 2: closing anchor tag */ + __( 'Increase conversions by auto-filling address information for customers during checkout. To enable GeoLocation Detection, %1$sUpgrade to Pro%2$s.', 'easy-digital-downloads' ), + '', + '' + ); + } + + /** + * Gets the register form setting. + * + * @since 3.3.0 + * @return array + */ + private function get_register_form() { + return 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' ), + ), + ); + } +} diff --git a/src/Admin/Settings/Tabs/General.php b/src/Admin/Settings/Tabs/General.php new file mode 100644 index 00000000000..4164dad7d46 --- /dev/null +++ b/src/Admin/Settings/Tabs/General.php @@ -0,0 +1,286 @@ + 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' => __( 'Thousands Separator', 'easy-digital-downloads' ), + 'desc' => __( 'The symbol to separate thousands. 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' => __( '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_toggle', + ), + 'enable_public_request_logs' => $this->get_enable_public_request_logs(), + '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', + ), + ), + ); + } + + /** + * Gets the disable public requests logs setting. + * + * @since 3.2.8 + * @return array + */ + private function get_enable_public_request_logs() { + $link = edd_get_admin_url( + array( + 'page' => 'edd-tools', + 'tab' => 'logs', + 'view' => 'api_requests', + ) + ); + + return array( + 'id' => 'enable_public_request_logs', + 'name' => __( 'Request Logs', 'easy-digital-downloads' ), + 'check' => __( 'Log public API requests.', 'easy-digital-downloads' ), + 'desc' => sprintf( + /* translators: 1: opening anchor tag, 2: closing anchor tag */ + __( 'Authenticated requests to the EDD API are always logged. %1$sView the API request logs.%2$s', 'easy-digital-downloads' ), + '', + '' + ), + 'type' => 'checkbox_toggle', + ); + } +} diff --git a/src/Admin/Settings/Tabs/Licenses.php b/src/Admin/Settings/Tabs/Licenses.php new file mode 100644 index 00000000000..bb6d32b247e --- /dev/null +++ b/src/Admin/Settings/Tabs/Licenses.php @@ -0,0 +1,41 @@ + 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' ), + 'check' => __( 'Allow customers to use multiple discounts on the same purchase?', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + ), + ), + ); + } +} diff --git a/src/Admin/Settings/Tabs/Misc.php b/src/Admin/Settings/Tabs/Misc.php new file mode 100644 index 00000000000..78a49769d37 --- /dev/null +++ b/src/Admin/Settings/Tabs/Misc.php @@ -0,0 +1,283 @@ +id}_sanitize", array( $this, 'sanitize' ) ); + } + + /** + * Updates the documentation link. + * + * @since 3.3.0 + * @param string $link The current documentation link. + * @return string + */ + public function update_docs_link( $link ) { + if ( $this->is_admin_page( 'settings', 'misc' ) && 'file_downloads' === $this->get_section() ) { + return 'https://easydigitaldownloads.com/docs/misc-settings/'; + } + + return parent::update_docs_link( $link ); + } + + /** + * Register the settings for this tab. + * + * @since 3.1.4 + * @return array + */ + protected function register() { + + return array( + 'main' => array( + 'debug_mode' => array( + 'id' => 'debug_mode', + 'name' => __( 'Debug Mode', 'easy-digital-downloads' ), + 'check' => __( 'Record important information to the debug log while troubleshooting.', 'easy-digital-downloads' ) . ' ' . $this->get_debug_log_link(), + 'type' => 'checkbox_toggle', + ), + 'session_handling' => array( + 'id' => 'session_handling', + 'name' => __( 'Session Handling', 'easy-digital-downloads' ), + 'type' => 'select', + 'std' => get_option( 'edd_session_handling', 'php' ), + 'options' => array( + 'php' => __( 'PHP Sessions', 'easy-digital-downloads' ), + 'db' => __( 'Database Sessions', 'easy-digital-downloads' ), + ), + 'desc' => __( 'Choose how you want to handle sessions. PHP based sessions are generally faster, but if you are experiencing issues with empty carts, database sessions may be more reliable.', 'easy-digital-downloads' ), + ), + 'disable_styles' => array( + 'id' => 'disable_styles', + 'name' => __( 'Disable Styles', 'easy-digital-downloads' ), + 'check' => __( 'Disable general EDD core styles for buttons, checkout fields, product pages, and other elements. EDD blocks will still load minimal styles.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + '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: Downloads plural label */ + 'check' => 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_toggle', + 'desc' => '', + ), + 'uninstall_on_delete' => array( + 'id' => 'uninstall_on_delete', + 'name' => __( 'Remove Data on Uninstall', 'easy-digital-downloads' ), + 'check' => __( 'Completely remove all EDD core data when the plugin is deleted.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + ), + ), + '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' ), + 'check' => __( '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_toggle', + ), + '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' => _x( + '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.', + "Tooltip Display: Quotations must use escaped single quotes, for example \'forced\'", + '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: Download singular label */ + '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: Download singular label */ + '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' => __( 'Limit File Access', 'easy-digital-downloads' ), + 'check' => __( 'Only give customers access to download links right after they make a purchase.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + 'tooltip_title' => __( 'Limiting Access', 'easy-digital-downloads' ), + 'tooltip_desc' => _x( + 'This will prevent customers from viewing download links on your site after their initial purchase session has expired. This does not restrict the number of times a file can be downloaded; you can set a limit on the number of times a user can download a file with the \'File Download Limit\' setting.', + "It is important to escape any quotations within this string, specifically \'File Download Limit\'", + 'easy-digital-downloads' + ), + ), + ), + ); + } + + /** + * Save the session handling setting. + * + * @since 3.3.0 + * @param array $input The form data. + * @return array + */ + public function sanitize( $input ) { + if ( empty( $input['session_handling'] ) ) { + return $input; + } + + update_option( 'edd_session_handling', $input['session_handling'] ); + unset( $input['session_handling'] ); + + return $input; + } + + /** + * 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..404e07f1412 --- /dev/null +++ b/src/Admin/Settings/Tabs/Privacy.php @@ -0,0 +1,179 @@ + 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' => __( 'Customers must agree to your privacy policy before purchasing.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + ), + '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 your Privacy Policy on checkout.', 'easy-digital-downloads' ) . ' ' . __( 'Set your Privacy Policy here', 'easy-digital-downloads' ) . '.', + 'type' => 'checkbox_toggle', + ), + ), + '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' => __( 'Customers must agree to your terms before purchasing.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + ), + '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..8f132258ceb --- /dev/null +++ b/src/Admin/Settings/Tabs/Tab.php @@ -0,0 +1,168 @@ +get_id(), $this->register() ); + } + + /** + * Updates the documentation link. + * + * @since 3.3.0 + * @param string $link The current documentation link. + * @return string + */ + public function update_docs_link( $link ) { + return $link; + } + + /** + * Get the ID for this tab. + * + * @since 3.1.4 + * + * @throws \Exception If the ID property is not set. + * + * @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' ); + } + + /** + * Gets the pass ID. + * + * @since 3.2.8 + * @return int + */ + protected function get_pass_id() { + if ( is_null( $this->pass_id ) ) { + $pass_manager = new \EDD\Admin\Pass_Manager(); + $this->pass_id = $pass_manager->highest_pass_id; + } + + return $this->pass_id; + } + + /** + * Checks if the current page is an admin page. + * + * @since 3.3.3 + * @param string $page The page to check. + * @param string $view The view to check. + * @return bool + */ + protected function is_admin_page( $page = 'settings', $view = '' ) { + if ( ! function_exists( 'edd_is_admin_page' ) ) { + return false; + } + + return edd_is_admin_page( $page, $view ); + } + + /** + * Gets the current tab. + * + * @since 3.3.3 + * @return string + */ + protected function get_tab() { + return filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_SPECIAL_CHARS ); + } + + /** + * Gets the current section. + * + * @since 3.3.3 + * @return string + */ + protected function get_section() { + return filter_input( INPUT_GET, 'section', FILTER_SANITIZE_SPECIAL_CHARS ); + } +} diff --git a/src/Admin/Settings/Tabs/Taxes.php b/src/Admin/Settings/Tabs/Taxes.php new file mode 100644 index 00000000000..983cbb1b83f --- /dev/null +++ b/src/Admin/Settings/Tabs/Taxes.php @@ -0,0 +1,128 @@ + array( + 'enable_taxes' => array( + 'id' => 'enable_taxes', + 'name' => __( 'Enable Taxes', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + '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' => __( 'Some countries require a notice that product prices include tax.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + ), + '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..6eb482deee6 --- /dev/null +++ b/src/Admin/Settings/WP_SMTP.php @@ -0,0 +1,249 @@ + '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', + 'edd_email_manager_bottom' => 'link_to_settings', + ); + } + + /** + * 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 ( ! function_exists( 'edd_is_admin_page' ) ) { + return $settings; + } + + if ( ! edd_is_admin_page( 'emails' ) ) { + return $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; + } + ?> + +
    +
    + is_smtp_configured() ) { + return; + } + + printf( + '%s', + esc_url( + edd_get_admin_url( + array( + 'page' => 'edd-emails', + 'tab' => 'settings', + ) + ) + ), + esc_html__( 'Ensure your emails are always delivered with WP Mail SMTP', 'easy-digital-downloads' ), + esc_url( EDD_PLUGIN_URL . 'assets/images/promo/brands/plugin-smtp.png' ) + ); + } + + /** + * Gets the button parameters. + * + * @return array + */ + private function get_button_parameters() { + $button = array(); + // If neither the lite nor pro plugin is installed, the button will prompt to install and activate the lite plugin. + if ( ! $this->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' ); + if ( 'mail' === $mailer ) { + return false; + } + + $mailer_object = wp_mail_smtp()->get_providers()->get_mailer( $mailer, $phpmailer ); + + return $mailer_object && $mailer_object->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() { + 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/Cron.php b/src/Admin/SiteHealth/Cron.php new file mode 100644 index 00000000000..e47502d9713 --- /dev/null +++ b/src/Admin/SiteHealth/Cron.php @@ -0,0 +1,83 @@ + __( 'Easy Digital Downloads — Cron Events', 'easy-digital-downloads' ), + 'fields' => $this->get_data(), + ); + } + + /** + * Gets the array of cron data. + * + * @since 3.3.0 + * @return array + */ + private function get_data() { + $data = array(); + $schedules = array( + 'daily' => 'edd_daily_scheduled_events', + 'weekly' => 'edd_weekly_scheduled_events', + 'email' => 'edd_email_summary_cron', + ); + if ( edd_get_component( 'session' ) ) { + $schedules['sessions'] = 'edd_cleanup_sessions'; + } + if ( edd_is_gateway_active( 'stripe' ) ) { + $schedules['stripe'] = 'edds_cleanup_rate_limiting_log'; + } + foreach ( $schedules as $key => $schedule ) { + $data[ $key ] = array( + 'label' => $schedule, + 'value' => $this->get_next_scheduled( $schedule ), + ); + } + + return $data; + } + + /** + * Gets the date for the next scheduled event. + * + * @since 3.3.0 + * @param string $event The event to check. + * @return string + */ + private function get_next_scheduled( $event ) { + $timestamp = wp_next_scheduled( $event ); + if ( ! $timestamp ) { + return 'Not Scheduled'; + } + + if ( defined( 'WP_DISABLE_CRON' ) && ! empty( WP_DISABLE_CRON ) ) { + return 'Cron Disabled'; + } + + return sprintf( + '%s (in %s)', + edd_date_i18n( $timestamp, 'Y-m-d H:i:s' ), + human_time_diff( $timestamp ) + ); + } +} diff --git a/src/Admin/SiteHealth/Direct.php b/src/Admin/SiteHealth/Direct.php new file mode 100644 index 00000000000..07f32f859ed --- /dev/null +++ b/src/Admin/SiteHealth/Direct.php @@ -0,0 +1,336 @@ + __( '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'] = $this->get_action_button( + 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: opening tag, 2. 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'] = $this->get_action_button( + 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 = new \EDD\Utils\RemoteRequest( trailingslashit( edd_get_upload_url() ) . 'index.php' ); + if ( 403 === $check->code ) { + 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'] = $this->get_action_button( + 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: %s: Uploads directory */ + __( 'The .htaccess file is missing from: %s', 'easy-digital-downloads' ), + '' . $upload_directory . '' + ), + sprintf( + /* translators: %s: Uploads directory */ + __( 'First, please re-save 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'] = $this->get_action_button( + 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['label'] = __( 'Your store is not accepting payments', 'easy-digital-downloads' ); + $result['description'] = sprintf( + '

    %1$s

    %2$s %3$s

    ', + __( + 'To process orders that require payment, you must have a gateway enabled.', + 'easy-digital-downloads' + ), + __( + 'A gateway is a service, such as PayPal or Stripe, that allows your store to accept payments.', + 'easy-digital-downloads' + ), + __( + 'Stores that offer multiple ways for their customers to pay see higher conversion rates.', + 'easy-digital-downloads' + ) + ); + + $result['actions'] = $this->get_action_button( + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + ) + ), + __( 'Configure a Gateway', '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..da91b62cbf7 --- /dev/null +++ b/src/Admin/SiteHealth/General.php @@ -0,0 +1,216 @@ +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', + ), + 'checkout_registration' => array( + 'label' => 'Customer Registration', + 'value' => $this->get_checkout_type(), + ), + 'checkout_forms' => array( + 'label' => 'Checkout Forms', + 'value' => $this->get_checkout_forms(), + ), + '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_position' => 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)', + ), + 'rest_enabled' => array( + 'label' => 'REST API', + 'value' => $this->is_rest_api_enabled( 'wp/v2/edd-downloads' ) ? 'Accessible' : 'Not Accessible', + ), + 'paypal_rest_available' => array( + 'label' => 'PayPal REST Endpoints', + 'value' => $this->is_rest_api_enabled( 'edd/webhooks/v1/paypal/webhook-test' ) ? 'Accessible' : 'Not Accessible', + ), + ), + ); + } + + /** + * 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'; + } + + /** + * Test if the REST API is accessible. + * + * The REST API might be inaccessible due to various security measures, + * or it might be completely disabled by a plugin. + * + * @since 3.2.0 + * @return bool + */ + private function is_rest_api_enabled( $endpoint = '' ) { + if ( empty( $endpoint ) ) { + return; + } + + $checker = new \EDD\Utils\RESTChecker( $endpoint ); + + return $checker->is_enabled(); + } + + /** + * Retrieves the checkout type. + * + * @since 3.3.3 + * @return string The checkout type. + */ + private function get_checkout_type() { + $registration = edd_get_option( 'logged_in_only', false ); + + if ( 'auto' === $registration ) { + return 'Automatically register new user accounts'; + } + + if ( 'required' === $registration ) { + return 'Customers must log in or create an account to purchase'; + } + + return 'Allow customers to place orders without an account'; + } + + /** + * Get the checkout forms. + * + * @since 3.3.3 + * @return string + */ + private function get_checkout_forms() { + $form = edd_get_option( 'show_register_form', 'none' ); + $labels = array( + 'both' => 'Registration and Login Forms', + 'registration' => 'Registration Form Only', + 'login' => 'Login Form Only', + 'none' => 'None', + ); + + return isset( $labels[ $form ] ) ? $labels[ $form ] : __( 'Unknown', 'easy-digital-downloads' ); + } +} diff --git a/src/Admin/SiteHealth/Information.php b/src/Admin/SiteHealth/Information.php new file mode 100644 index 00000000000..9273f49539e --- /dev/null +++ b/src/Admin/SiteHealth/Information.php @@ -0,0 +1,87 @@ + '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(), + 'edd_cron' => new Cron(), + ); + + $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..a951b00a675 --- /dev/null +++ b/src/Admin/SiteHealth/Pages.php @@ -0,0 +1,80 @@ + __( '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', '' ); + $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 ) : '', + ), + 'confirmation_uri' => array( + 'label' => 'Confirmation Page', + 'value' => get_permalink( edd_get_option( 'confirmation_page', '' ) ), + ), + 'success_uri' => array( + 'label' => 'Receipt (Success) Page', + 'value' => get_permalink( edd_get_option( 'success_page', '' ) ), + ), + 'failure_uri' => array( + 'label' => 'Failure Page', + 'value' => get_permalink( edd_get_option( 'failure_page', '' ) ), + ), + 'order_history_uri' => array( + 'label' => 'Order History Page', + 'value' => get_permalink( edd_get_option( 'purchase_history_page', '' ) ), + ), + 'login_uri' => array( + 'label' => 'Login Page', + 'value' => get_permalink( edd_get_option( 'login_page', '' ) ), + ), + 'login_redirect_uri' => array( + 'label' => 'Login Redirect Page', + 'value' => get_permalink( edd_get_option( 'login_redirect_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..64eeefa38fc --- /dev/null +++ b/src/Admin/SiteHealth/Sessions.php @@ -0,0 +1,135 @@ + __( '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( + 'session_enabled' => array( + 'label' => 'PHP Session Enabled', + 'value' => defined( PHP_SESSION_DISABLED ) && PHP_SESSION_DISABLED !== session_status() ? 'Enabled' : 'Disabled', + ), + 'session_type' => array( + 'label' => 'Session Type', + 'value' => edd_get_component( 'session' ) ? 'Database' : 'PHP', + ), + ); + + $database_sessions = $this->get_sessions(); + $session_data = $this->get_session_data(); + + return array_merge( $data, $database_sessions, $session_data ); + } + + /** + * Gets the data from the $_SESSION global. + * + * @since 3.1.2 + * @return false|array + */ + private function get_session_data() { + if ( ! isset( $_SESSION ) ) { + return array(); + } + + 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', + ), + ); + } + + /** + * Gets the number and status of sessions. + * + * @since 3.3.0 + * @return array + */ + private function get_sessions() { + if ( ! edd_get_component( 'session' ) ) { + return array(); + } + + $query = new \EDD\Database\Queries\Session(); + $total = $query->query( + array( + 'count' => true, + ) + ); + $expired = $query->query( + array( + 'count' => true, + 'session_expiry__compare' => array( + 'relation' => 'AND', + array( + 'value' => time(), + 'compare' => '<', + ), + ), + ) + ); + + return array( + 'sessions_all' => array( + 'label' => 'All Sessions', + 'value' => $total, + ), + 'sessions_active' => array( + 'label' => 'Active Sessions', + 'value' => $total - $expired, + ), + 'sessions_expired' => array( + 'label' => 'Expired Sessions', + 'value' => $expired, + ), + ); + } +} diff --git a/src/Admin/SiteHealth/Tables.php b/src/Admin/SiteHealth/Tables.php new file mode 100644 index 00000000000..0947142d534 --- /dev/null +++ b/src/Admin/SiteHealth/Tables.php @@ -0,0 +1,80 @@ + __( '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 $thing->exists() ? + sprintf( '%s / %s', $thing->get_version(), $thing->count() ) : + 'Error — table is missing'; + } +} 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..2a3c21bc313 --- /dev/null +++ b/src/Admin/SiteHealth/Test.php @@ -0,0 +1,43 @@ + __( 'Easy Digital Downloads', 'easy-digital-downloads' ), + 'color' => 'blue', + ); + } + + /** + * Build the markup for the action button. + * + * @since 3.2.7 + * + * @param string $url The URL to link to. + * @param string $label The label for the button. + * + * @return string + */ + protected function get_action_button( $url = '', $label = '' ) { + return sprintf( + '%2$s', + esc_url( $url ), + esc_html( $label ) + ); + } +} 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/Admin/Styles.php b/src/Admin/Styles.php new file mode 100644 index 00000000000..f7d297f5cf2 --- /dev/null +++ b/src/Admin/Styles.php @@ -0,0 +1,50 @@ + 'add_body_class', + ); + } + + /** + * Adds the body class for our admin pages. + * + * @since 3.2.4 + * + * @param string $classes The current body classes. + * @return string + */ + public function add_body_class( $classes ) { + if ( ! edd_is_admin_page() ) { + return $classes; + } + + $classes .= ' edd-admin-page '; + + return $classes; + } +} diff --git a/src/Admin/Tools/Screen.php b/src/Admin/Tools/Screen.php new file mode 100644 index 00000000000..d74b20799ea --- /dev/null +++ b/src/Admin/Tools/Screen.php @@ -0,0 +1,121 @@ +render(); + ?> + +
    +
    +
    + +
    +
    + __( 'General', 'easy-digital-downloads' ), + 'api_keys' => __( 'API Keys', 'easy-digital-downloads' ), + 'betas' => __( 'Beta Versions', 'easy-digital-downloads' ), + 'logs' => __( 'Logs', 'easy-digital-downloads' ), + 'system_info' => __( 'System Info', 'easy-digital-downloads' ), + 'debug_log' => __( 'Debug Log', 'easy-digital-downloads' ), + 'import_export' => __( 'Import/Export', 'easy-digital-downloads' ), + ); + + // Unset the betas tab if not allowed. + if ( count( edd_get_beta_enabled_extensions() ) <= 0 ) { + unset( $tabs['betas'] ); + } + + self::$tabs = apply_filters( 'edd_tools_tabs', $tabs ); + + self::$tabs['system_info'] = array( + 'name' => self::$tabs['system_info'], + 'url' => self::get_system_info_link(), + ); + } + + return self::$tabs; + } + + /** + * Gets the active tab. + * + * @since 3.3.0 + * @return string + */ + private static function get_active_tab() { + $active_tab = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_SPECIAL_CHARS ); + + return $active_tab ?? 'general'; + } + + /** + * Gets the system info link. + * + * @since 3.3.0 + * @return string + */ + private static function get_system_info_link() { + return add_query_arg( + array( + 'tab' => 'debug', + 'edd' => 'filter', + ), + admin_url( 'site-health.php' ) + ); + } +} diff --git a/src/Admin/Upgrades/v3/Base.php b/src/Admin/Upgrades/v3/Base.php new file mode 100644 index 00000000000..4f8ffac774b --- /dev/null +++ b/src/Admin/Upgrades/v3/Base.php @@ -0,0 +1,220 @@ +can_export() ) { + wp_die( + esc_html__( 'You do not have permission to run this upgrade.', 'easy-digital-downloads' ), + esc_html__( 'Error', 'easy-digital-downloads' ), + array( + 'response' => 403, + ) + ); + } + + $had_data = $this->get_data(); + + if ( $had_data ) { + $this->done = false; + // Save the *next* step to do. + update_option( sprintf( 'edd_v3_migration_%s_step', sanitize_key( $this->get_upgrade_step() ) ), $this->step + 1 ); + return true; + } else { + $this->done = true; + $this->message = $this->completed_message; + + // We may have multiple upgrades to mark as completed... + if ( is_array( $this->upgrade ) ) { + foreach ( $this->upgrade as $upgrade ) { + edd_set_upgrade_complete( $upgrade ); + } + } else { + edd_set_upgrade_complete( $this->upgrade ); + } + + delete_option( sprintf( 'edd_v3_migration_%s_step', sanitize_key( $this->get_upgrade_step() ) ) ); + edd_v30_is_migration_complete(); + return false; + } + } + + /** + * Set the headers. + * + * @since 3.0 + */ + public function headers() { + edd_set_time_limit(); + } + + /** + * Perform the migration. + * + * @since 3.0 + * + * @return void + */ + public function export() { + + // Set headers. + $this->headers(); + + edd_die(); + } + + /** + * Return the global database interface. + * + * @since 3.0 + * @access protected + * @static + * + * @return \wpdb|\stdClass + */ + protected static function get_db() { + return isset( $GLOBALS['wpdb'] ) + ? $GLOBALS['wpdb'] + : new \stdClass(); + } + + /** + * Set properties specific to the export. + * + * @since 3.0 + * + * @param array $request Form data passed into the batch processor. + */ + public function set_properties( $request ) { + } + + /** + * Allow for pre-fetching of data for the remainder of the batch processor. + * + * @since 3.0 + */ + public function pre_fetch() { + } + + /** + * Gets the next upgrade step (used for saving to an option). + * + * @since 3.2.2 + * @return string + */ + private function get_upgrade_step() { + if ( is_array( $this->upgrade ) ) { + return reset( $this->upgrade ); + } + + return $this->upgrade; + } +} diff --git a/src/Admin/Upgrades/v3/Customer_Addresses.php b/src/Admin/Upgrades/v3/Customer_Addresses.php new file mode 100644 index 00000000000..c51551235d2 --- /dev/null +++ b/src/Admin/Upgrades/v3/Customer_Addresses.php @@ -0,0 +1,91 @@ +completed_message = __( 'Customer addresses migration completed successfully.', 'easy-digital-downloads' ); + $this->upgrade = 'migrate_customer_addresses'; + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + $offset = ( $this->step - 1 ) * $this->per_step; + + $results = $this->get_db()->get_results( $this->get_db()->prepare( + "SELECT * + FROM {$this->get_db()->usermeta} + WHERE meta_key = %s + ORDER BY umeta_id ASC + LIMIT %d, %d", + esc_sql( '_edd_user_address' ), $offset, $this->per_step + ) ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + Data_Migrator::customer_addresses( $result ); + } + + return true; + } + + return false; + } + + /** + * Calculate the percentage completed. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + $total = $this->get_db()->get_var( $this->get_db()->prepare( "SELECT COUNT(umeta_id) AS count FROM {$this->get_db()->usermeta} WHERE meta_key = %s", esc_sql( '_edd_user_address' ) ) ); + + if ( empty( $total ) ) { + $total = 0; + } + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( $this->per_step * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } +} diff --git a/src/Admin/Upgrades/v3/Customer_Email_Addresses.php b/src/Admin/Upgrades/v3/Customer_Email_Addresses.php new file mode 100644 index 00000000000..27f1d79cf48 --- /dev/null +++ b/src/Admin/Upgrades/v3/Customer_Email_Addresses.php @@ -0,0 +1,144 @@ +completed_message = __( 'Customer email addresses migration completed successfully.', 'easy-digital-downloads' ); + $this->upgrade = 'migrate_customer_email_addresses'; + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + $success = false; + $offset = ( $this->step - 1 ) * $this->per_step; + + $results = $this->get_db()->get_results( + $this->get_db()->prepare( + "SELECT * + FROM {$this->get_db()->edd_customermeta} + WHERE meta_key = %s + LIMIT %d, %d", + esc_sql( 'additional_email' ), + $offset, + $this->per_step + ) + ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + // Check if email has already been migrated. + if ( ! empty( $result->edd_customer_id ) && $result->meta_value ) { + $number_results = edd_count_customer_email_addresses( + array( + 'customer_id' => $result->edd_customer_id, + 'email' => $result->meta_value, + ) + ); + if ( $number_results > 0 ) { + continue; + } + } + + Data_Migrator::customer_email_addresses( $result ); + } + + $success = true; + } + + // Query customers without email address objects. + $customers_without_emails = $this->get_db()->get_results( + $this->get_db()->prepare( + "SELECT * + FROM {$this->get_db()->edd_customers} + WHERE email != '' + AND email NOT IN ( + SELECT email + FROM {$this->get_db()->edd_customer_email_addresses} + ) + LIMIT %d", + $this->per_step + ) + ); + + if ( $customers_without_emails ) { + foreach ( $customers_without_emails as $customer ) { + $customer_has_primary = edd_count_customer_email_addresses( + array( + 'customer_id' => $customer->id, + 'type' => 'primary', + ) + ); + edd_add_customer_email_address( + array( + 'customer_id' => $customer->id, + 'email' => $customer->email, + 'date_created' => $customer->date_created, + 'type' => $customer_has_primary ? 'secondary' : 'primary', + ) + ); + } + + $success = true; + } + + return $success; + } + + /** + * Calculate the percentage completed. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + $total = $this->get_db()->get_var( $this->get_db()->prepare( "SELECT COUNT(meta_id) AS count FROM {$this->get_db()->edd_customermeta} WHERE meta_key = %s", esc_sql( 'additional_email' ) ) ); + + if ( empty( $total ) ) { + $total = 0; + } + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( $this->per_step * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } +} diff --git a/src/Admin/Upgrades/v3/Customer_Notes.php b/src/Admin/Upgrades/v3/Customer_Notes.php new file mode 100644 index 00000000000..b66c541acd9 --- /dev/null +++ b/src/Admin/Upgrades/v3/Customer_Notes.php @@ -0,0 +1,91 @@ +completed_message = __( 'Customer notes migration completed successfully.', 'easy-digital-downloads' ); + $this->upgrade = 'migrate_customer_notes'; + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + $offset = ( $this->step - 1 ) * $this->per_step; + + $results = $this->get_db()->get_results( $this->get_db()->prepare( + "SELECT * + FROM {$this->get_db()->edd_customers} + LIMIT %d, %d", + $offset, $this->per_step + ) ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + Data_Migrator::customer_notes( $result ); + } + + return true; + } + + return false; + } + + /** + * Calculate the percentage completed. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + $total = $this->get_db()->get_var( "SELECT COUNT(id) AS count FROM {$this->get_db()->edd_customers}" ); + + if ( empty( $total ) ) { + $total = 0; + } + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( $this->per_step * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } +} diff --git a/src/Admin/Upgrades/v3/Data_Migrator.php b/src/Admin/Upgrades/v3/Data_Migrator.php new file mode 100644 index 00000000000..ee0d503ccf3 --- /dev/null +++ b/src/Admin/Upgrades/v3/Data_Migrator.php @@ -0,0 +1,1588 @@ +meta_value ); + + $user_id = absint( $data->user_id ); + + $customer = edd_get_customer_by( 'user_id', $user_id ); + + $address = wp_parse_args( + $address, + array( + 'line1' => '', + 'line2' => '', + 'city' => '', + 'state' => '', + 'zip' => '', + 'country' => '', + ) + ); + + $address_to_check = array_filter( $address ); + + // Do not migrate empty addresses. + if ( empty( $address_to_check ) ) { + return; + } + + if ( $customer ) { + edd_maybe_add_customer_address( + $customer->id, + array( + 'is_primary' => true, + 'name' => $customer->name, + 'address' => $address['line1'], + 'address2' => $address['line2'], + 'city' => $address['city'], + 'region' => $address['state'], + 'postal_code' => $address['zip'], + 'country' => $address['country'], + 'date_created' => $customer->date_created, + ) + ); + } + } + + /** + * Customer email addresses. + * + * @since 3.0 + * + * @param object $data Data to migrate. + */ + public static function customer_email_addresses( $data = null ) { + + // Bail if no data passed. + if ( ! isset( $data->edd_customer_id ) || ! isset( $data->meta_value ) ) { + return; + } + + $customer = edd_get_customer( absint( $data->edd_customer_id ) ); + if ( ! $customer ) { + return; + } + + edd_add_customer_email_address( + array( + 'customer_id' => $customer->id, + 'email' => $data->meta_value, + 'date_created' => $customer->date_created, + ) + ); + } + + /** + * Customer notes. + * + * @since 3.0 + * + * @param object $data Data to migrate. + */ + public static function customer_notes( $data = null ) { + + // Bail if no data passed. + if ( ! $data ) { + return; + } + + $customer_id = absint( $data->id ); + + if ( property_exists( $data, 'notes' ) && ! empty( $data->notes ) ) { + $notes = array_reverse( array_filter( explode( "\n\n", $data->notes ) ) ); + + $notes = array_map( function( $val ) { + return explode( ' - ', $val ); + }, $notes ); + + if ( ! empty( $notes ) ) { + foreach ( $notes as $note ) { + try { + $date = isset( $note[0] ) + ? EDD()->utils->date( $note[0], edd_get_timezone_id() )->setTimezone( 'UTC' )->toDateTimeString() + : ''; + } catch ( \Exception $e ) { + // An empty date will be changed to current time in BerlinDB. + $date = ''; + } + + $note_content = isset( $note[1] ) + ? $note[1] + : ''; + + edd_add_note( array( + 'user_id' => 0, + 'object_id' => $customer_id, + 'object_type' => 'customer', + 'content' => $note_content, + 'date_created' => $date, + 'date_modified' => $date, + ) ); + } + } + } + } + + /** + * Discounts. + * + * @since 3.0 + * + * @param object $data Data to migrate. + */ + public static function discounts( $data = null ) { + + // Bail if no data passed. + if ( ! $data ) { + return; + } + + $data = get_post( $data->ID ); + + $args = array(); + $meta = get_post_custom( $data->ID ); + $meta_to_migrate = array(); + $core_meta = array( + 'code', + 'name', + 'status', + 'uses', + 'max_uses', + 'amount', + 'start', + 'expiration', + 'type', + 'min_price', + 'product_reqs', + 'product_condition', + 'excluded_products', + 'is_not_global', + 'is_single_use', + ); + + foreach ( $meta as $key => $value ) { + $value = maybe_unserialize( $value[0] ); + if ( false === strpos( $key, '_edd_discount' ) ) { + + // This is custom meta from another plugin that needs to be migrated to the new meta table. + $meta_to_migrate[ $key ] = $value; + continue; + } + $meta_key = str_replace( '_edd_discount_', '', $key ); + if ( ! in_array( $meta_key, $core_meta, true ) ) { + $meta_to_migrate[ $meta_key ] = $value; + continue; + } + + $args[ $meta_key ] = $value; + } + + // If the discount name was not stored in post_meta, use value from the WP_Post object. + if ( ! isset( $args['name'] ) ) { + $args['name'] = $data->post_title; + } + + $args['id'] = $data->ID; + $args['date_created'] = $data->post_date_gmt; + $args['date_modified'] = $data->post_modified_gmt; + + // Use edd_store_discount() so any legacy data is handled correctly. + $discount_id = edd_store_discount( $args ); + + // Migrate any additional meta. + if ( ! empty( $meta_to_migrate ) ) { + foreach ( $meta_to_migrate as $key => $value ) { + edd_add_adjustment_meta( $discount_id, $key, $value ); + } + } + } + + /** + * Logs. + * + * @since 3.0 + * + * @param object $data Data to migrate. + */ + public static function logs( $data = null ) { + global $wpdb; + + // Bail if no data passed. + if ( ! $data ) { + return; + } + + $meta_to_migrate = array(); + if ( 'file_download' === $data->slug ) { + $meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d", absint( $data->ID ) ) ); + + $post_meta = array(); + + foreach ( $meta as $meta_item ) { + $post_meta[ $meta_item->meta_key ] = maybe_unserialize( $meta_item->meta_value ); + } + + $log_data = array( + 'product_id' => $data->post_parent, + /* + * Custom Deliverables was overriding the file ID to be a string instead of an integer. The preg_replace + * allows us to try to salvage the file ID from that string. + */ + 'file_id' => isset( $post_meta['_edd_log_file_id'] ) ? preg_replace( '/[^0-9]/', '', $post_meta['_edd_log_file_id'] ) : 0, + 'order_id' => isset( $post_meta['_edd_log_payment_id'] ) ? $post_meta['_edd_log_payment_id'] : 0, + 'price_id' => isset( $post_meta['_edd_log_price_id'] ) ? $post_meta['_edd_log_price_id'] : 0, + 'customer_id' => isset( $post_meta['_edd_log_customer_id'] ) ? $post_meta['_edd_log_customer_id'] : 0, + 'ip' => isset( $post_meta['_edd_log_ip'] ) ? $post_meta['_edd_log_ip'] : '', + 'date_created' => $data->post_date_gmt, + 'date_modified' => $data->post_modified_gmt, + ); + + $meta_to_remove = array( + '_edd_log_file_id', + '_edd_log_payment_id', + '_edd_log_price_id', + '_edd_log_customer_id', + '_edd_log_ip', + '_edd_log_user_id', + ); + // If the log doesn't have a customer ID, but does have a user ID, keep the user ID as metadata. + if ( empty( $log_data['customer_id'] ) && ! empty( $post_meta['_edd_log_user_id'] ) && ! in_array( $post_meta['_edd_log_user_id'], array( 0, -1 ) ) ) { + $meta_to_remove = array_diff( $meta_to_remove, array( '_edd_log_user_id' ) ); + } + $meta_to_migrate = $post_meta; + $new_log_id = edd_add_file_download_log( $log_data ); + $add_meta_function = 'edd_add_file_download_log_meta'; + + /** + * Triggers after a file download log has been migrated. + * + * @since 3.0 + * + * @param int $new_log_id ID of the newly created log. + * @param object $data Data from the posts table. (Essentially a `WP_Post`, without being that object.) + * @param array $post_meta All meta associated with this log. + */ + do_action( 'edd_30_migrate_file_download_log', $new_log_id, $data, $post_meta ); + } elseif ( 'api_request' === $data->slug ) { + $meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d", absint( $data->ID ) ) ); + + $post_meta = array(); + + foreach ( $meta as $meta_item ) { + $post_meta[ $meta_item->meta_key ] = maybe_unserialize( $meta_item->meta_value ); + } + + $post_meta = wp_parse_args( + $post_meta, + array( + '_edd_log_request_ip' => '', + '_edd_log_user' => 0, + '_edd_log_key' => 'public', + '_edd_log_token' => 'public', + '_edd_log_version' => '', + '_edd_log_time' => '', + ) + ); + + if ( empty( $post_meta['_edd_log_token'] ) ) { + $post_meta['_edd_log_token'] = 'public' === $post_meta['_edd_log_key'] ? 'public' : ''; + } + + $log_data = array( + 'ip' => $post_meta['_edd_log_request_ip'], + 'user_id' => $post_meta['_edd_log_user'], + 'api_key' => $post_meta['_edd_log_key'], + 'token' => $post_meta['_edd_log_token'], + 'version' => $post_meta['_edd_log_version'], + 'time' => $post_meta['_edd_log_time'], + 'request' => $data->post_excerpt, + 'error' => $data->post_content, + 'date_created' => $data->post_date_gmt, + 'date_modified' => $data->post_modified_gmt, + ); + + $meta_to_remove = array( + '_edd_log_request_ip', + '_edd_log_user', + '_edd_log_key', + '_edd_log_token', + '_edd_log_version', + '_edd_log_time', + ); + $meta_to_migrate = $post_meta; + $new_log_id = edd_add_api_request_log( $log_data ); + $add_meta_function = 'edd_add_api_request_log_meta'; + } else { + $post_meta = get_post_custom( $data->ID ); + foreach ( $post_meta as $key => $value ) { + $meta_to_migrate[ $key ] = maybe_unserialize( $value[0] ); + } + + $log_data = array( + 'object_id' => $data->post_parent, + 'object_type' => 'download', + 'user_id' => ! empty( $meta_to_migrate['_edd_log_user'] ) ? $meta_to_migrate['_edd_log_user'] : $data->post_author, + 'type' => $data->slug, + 'title' => $data->post_title, + 'content' => $data->post_content, + 'date_created' => $data->post_date_gmt, + 'date_modified' => $data->post_modified_gmt, + ); + + $meta_to_remove = array( + '_edit_lock', + '_edd_log_user', + ); + + $new_log_id = edd_add_log( $log_data ); + $add_meta_function = 'edd_add_log_meta'; + } + + if ( ! is_callable( $add_meta_function ) || empty( $meta_to_migrate ) ) { + return; + } + + foreach ( $meta_to_migrate as $key => $value ) { + if ( ! in_array( $key, $meta_to_remove, true ) ) { + // Strip off `_edd_log_` prefix. + $key = str_replace( '_edd_log_', '', $key ); + + $add_meta_function( $new_log_id, $key, $value ); + } + } + } + + /** + * Order notes. + * + * @since 3.0 + * + * @param object $data Data to migrate. + */ + public static function order_notes( $data = null ) { + + // Bail if no data passed. + if ( ! $data ) { + return; + } + + $date_actions_run_note = __( 'After payment actions processed.', 'easy-digital-downloads' ); + if ( $date_actions_run_note === $data->comment_content ) { + $order = edd_get_order( $data->object_id ); + if ( $order->date_actions_run ) { + return; + } + } + + $note_data = array( + 'object_id' => $data->object_id, + 'object_type' => 'order', + 'date_created' => $data->comment_date_gmt, + 'date_modified' => $data->comment_date_gmt, + 'content' => $data->comment_content, + 'user_id' => $data->user_id, + ); + + $id = edd_add_note( $note_data ); + + $meta = get_comment_meta( $data->comment_ID ); + if ( ! empty( $meta ) ) { + foreach ( $meta as $key => $value ) { + edd_add_note_meta( $id, $key, $value ); + } + } + } + + /** + * Orders. + * + * @since 3.0 + * @param object $data Data to migrate. + */ + public static function orders( $data = null ) { + + // Bail if no data passed. + if ( ! $data ) { + return false; + } + + // Gets all the post meta for this payment. + $meta = get_post_custom( $data->ID ); + + $payment_meta = Serializer::maybe_unserialize( $meta['_edd_payment_meta'][0] ); + $user_info = isset( $payment_meta['user_info'] ) ? maybe_unserialize( $payment_meta['user_info'] ) : array(); + + // It is possible that for some reason the entire unserialized array is invalid, so before trying to use it, let's just verify we got an array back. + if ( ! is_array( $payment_meta ) ) { + // Dump this data to a file to ensure we keep it for later use. + edd_debug_log( '==== Failed Migrating Legacy Payment ID: ' . $data->ID . ' ====', true ); + edd_debug_log( 'Reason: Payment Meta Unserialization failed.', true ); + edd_debug_log( '- Post Data', true ); + foreach ( get_object_vars( $data ) as $key => $value ) { + edd_debug_log( '-- ' . $key . ': ' . $value, true ); + } + + edd_debug_log( '- Post Meta', true ); + foreach ( $meta as $key => $value_array ) { + edd_debug_log( '-- Meta Key: ' . $key, true ); + foreach ( $value_array as $value ) { + edd_debug_log( '--- ' . $value, true ); + } + } + + return false; + } + + // Some old EDD data has the user info serialized, but starting with something other than a: so it can't be unserialized + $user_info = self::fix_possible_serialization( $user_info ); + $user_info = maybe_unserialize( $user_info ); + + if ( ! is_array( $user_info ) ) { + $user_info = array(); + } + + /** + * Last chance to filter payment meta before we use it! + * Note: If modifying `cart_details`, then it's recommended that you first run + * `EDD\Admin\Upgrades\v3\Data_Migrator::fix_possible_serialization()` + * before making adjustments. + * + * @since 3.0 + * + * @param array $payment_meta Payment meta. + * @param int $payment_id ID of the payment. + * @param array $meta All post meta. + */ + $payment_meta = apply_filters( 'edd_30_migration_payment_meta', $payment_meta, $data->ID, $meta ); + + $order_number = isset( $meta['_edd_payment_number'][0] ) ? $meta['_edd_payment_number'][0] : ''; + $user_id = isset( $meta['_edd_payment_user_id'][0] ) && ! empty( $meta['_edd_payment_user_id'][0] ) ? $meta['_edd_payment_user_id'][0] : 0; + $ip = isset( $meta['_edd_payment_user_ip'][0] ) ? $meta['_edd_payment_user_ip'][0] : ''; + $mode = isset( $meta['_edd_payment_mode'][0] ) ? $meta['_edd_payment_mode'][0] : 'live'; + $gateway = isset( $meta['_edd_payment_gateway'][0] ) && ! empty( $meta['_edd_payment_gateway'][0] ) ? $meta['_edd_payment_gateway'][0] : 'manual'; + $customer_id = isset( $meta['_edd_payment_customer_id'][0] ) ? $meta['_edd_payment_customer_id'][0] : 0; + $date_completed = isset( $meta['_edd_completed_date'][0] ) ? $meta['_edd_completed_date'][0] : null; + $purchase_key = isset( $meta['_edd_payment_purchase_key'][0]) ? $meta['_edd_payment_purchase_key'][0] : false; + $purchase_email = isset( $meta['_edd_payment_user_email'][0] ) ? $meta['_edd_payment_user_email'][0] : $payment_meta['email']; + $date_actions_run = isset( $meta['_edd_complete_actions_run'][0] ) ? $meta['_edd_complete_actions_run'][0] : null; + + // Get the customer object + if ( ! empty( $customer_id ) ) { + $customer = edd_get_customer( $customer_id ); + } else if ( ! empty( $purchase_email ) ) { + $customer = edd_get_customer_by( 'email', $purchase_email ); + if ( $customer ) { + $customer_id = $customer->id; + } + } + + if ( false === $purchase_key ) { + $purchase_key = isset( $payment_meta['key'] ) ? $payment_meta['key'] : ''; + } + + // Do not use -1 as the user ID. + $user_id = ( -1 === $user_id ) + ? 0 + : $user_id; + + // Account for possible double serialization of the cart_details + $cart_details = isset( $payment_meta['cart_details'] ) ? maybe_unserialize( $payment_meta['cart_details'] ) : array(); + + // Some old EDD data has the cart details serialized, but starting with something other than a: so it can't be unserialized + $cart_details = self::fix_possible_serialization( $cart_details ); + + // Some old cart data does not contain subtotal or discount information. Normalize it. + $cart_details = self::normalize_cart_details( $cart_details ); + + // Account for possible double serialization of the cart_details + $cart_downloads = isset( $payment_meta['downloads'] ) ? maybe_unserialize( $payment_meta['downloads'] ) : array(); + + // Some old EDD data has the downloads serialized, but starting with something other than a: so it can't be unserialized + $cart_downloads = self::fix_possible_serialization( $cart_downloads ); + + // If the order status is 'publish' convert it to the new 'complete' status. + $order_status = 'publish' === $data->post_status ? 'complete' : $data->post_status; + + // If there are no items, and it's abandoned, just return, since this isn't a valid order. + if ( 'abandoned' === $order_status && empty( $cart_downloads ) && empty( $cart_details ) ) { + edd_debug_log( 'Skipping order ' . $data->ID . ' due to abandoned status and no products.', true ); + return false; + } + + $order_subtotal = 0; + $order_tax = 0; + $order_discount = 0; + $order_total = 0; + + // Track the total value of added fees in case the Order was initially migrated + // without _edd_payment_total or _edd_payment_tax and manual calculation was needed. + $order_fees_tax = 0; + $order_fees_total = 0; + $order_items_fees_tax = 0; + + // Retrieve the tax amount from metadata if available. + $meta_tax = isset( $meta['_edd_payment_tax'] ) + ? $meta['_edd_payment_tax'] + : false; + + if ( false !== $meta_tax ) { + $meta_tax = maybe_unserialize( $meta_tax ); + $order_tax = (float) $meta_tax[0]; + } + + $meta_total = false; + // Retrieve the total amount from metadata if available. + if ( isset( $meta['_edd_payment_total'] ) ) { + $meta_total = maybe_unserialize( $meta['_edd_payment_total'] ); + $order_total = (float) $meta_total[0]; + } elseif ( isset( $payment_meta['amount'] ) ) { + $meta_total = maybe_unserialize( $payment_meta['amount'] ); + $order_total = (float) $meta_total; + } + + // In some cases (very few) there is no cart details...so we have to just avoid this part. + if ( ! empty( $cart_details ) && is_array( $cart_details ) ) { + + // Loop through the items in the purchase to build the totals. + foreach ( $cart_details as $cart_item ) { + $order_subtotal += $cart_item['subtotal']; + + // Add the cart line item tax amount if a total is not available on the order. + if ( false === $meta_tax ) { + $order_tax += $cart_item['tax']; + } + + $order_discount += $cart_item['discount']; + + // Add the cart line item price amount (includes tax, order item fee, _but not order item fee tax_) + // if a total is not available on the order. + if ( false === $meta_total ) { + $order_total += $cart_item['price']; + } + } + + } + + // Account for a situation where the post_date_gmt is set to 0000-00-00 00:00:00 + $date_created_gmt = $data->post_date_gmt; + if ( '0000-00-00 00:00:00' === $date_created_gmt ) { + + $date_created_gmt = new \DateTime( $data->post_date ); + $modified_time = new \DateTime( $data->post_modified ); + $modified_time_gmt = new \DateTime( $data->post_modified_gmt ); + + if ( $modified_time != $modified_time_gmt ) { + $diff = $modified_time_gmt->diff( $modified_time ); + + $time_diff = 'PT'; + + // Add hours to the offset string. + if ( ! empty( $diff->h ) ) { + $time_diff .= $diff->h . 'H'; + } + + // Add minutes to the offset string. + if ( ! empty( $diff->i ) ) { + $time_diff .= $diff->i . 'M'; + } + + // Account for -/+ GMT offsets. + try { + if ( 1 === $diff->invert ) { + $date_created_gmt->add( new \DateInterval( $time_diff ) ); + } else { + $date_created_gmt->sub( new \DateInterval( $time_diff ) ); + } + } catch ( \Exception $e ) { + + } + } + + $date_created_gmt = $date_created_gmt->format('Y-m-d H:i:s'); + } + + // Maybe convert the date completed to UTC or backfill the date_completed. + $non_completed_statuses = apply_filters( 'edd_30_noncomplete_statuses', edd_get_incomplete_order_statuses() ); + if ( ! in_array( $order_status, $non_completed_statuses, true ) ) { + + if ( ! empty( $date_completed ) ) { // Update the data_completed to the UTC. + try { + $date_completed = EDD()->utils->date( $date_completed, edd_get_timezone_id() )->setTimezone( 'UTC' )->toDateTimeString(); + } catch ( \Exception $e ) { + $date_completed = $date_created_gmt; + } + } elseif ( is_null( $date_completed ) ) { // Backfill a missing date_completed (for things like recurring payments). + $date_completed = $date_created_gmt; + } + + } + + if ( 'manual_purchases' === $gateway && isset( $meta['_edd_payment_total'][0] ) ) { + $gateway = 'manual'; + $order_total = $meta['_edd_payment_total'][0]; + } + + /* + * Build up the order address data. Actual insertion happens later, but we need this now to figure out the tax rate. + */ + + // First & last name. + $user_info['first_name'] = ! empty( $user_info['first_name'] ) + ? $user_info['first_name'] + : ''; + $user_info['last_name'] = ! empty( $user_info['last_name'] ) + ? $user_info['last_name'] + : ''; + + // Add order address. + $user_info['address'] = ! empty( $user_info['address'] ) + ? $user_info['address'] + : array(); + + $user_info['address'] = wp_parse_args( $user_info['address'], array( + 'line1' => '', + 'line2' => '', + 'city' => '', + 'zip' => '', + 'country' => '', + 'state' => '', + ) ); + + $order_address_data = array( + 'name' => trim( $user_info['first_name'] . ' ' . $user_info['last_name'] ), + 'address' => 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'] : '', + 'region' => isset( $user_info['address']['state'] ) ? $user_info['address']['state'] : '', + 'country' => isset( $user_info['address']['country'] ) && array_key_exists( strtoupper( $user_info['address']['country'] ), edd_get_country_list() ) + ? $user_info['address']['country'] + : '', + 'postal_code' => isset( $user_info['address']['zip'] ) ? $user_info['address']['zip'] : '', + 'date_created' => $date_created_gmt, + ); + + $tax_rate_id = null; + $tax_rate = isset( $meta['_edd_payment_tax_rate'][0] ) + ? (float) $meta['_edd_payment_tax_rate'][0] + : 0.00; + + /* + * Previously tax rates were stored as a decimal (e.g. `0.2`) but they're now stored as a percentage + * (e.g. `20`). So we need to convert. + */ + if ( $tax_rate < 1 ) { + $tax_rate = $tax_rate * 100; + } + + $set_tax_rate_meta = false; + + if ( ! empty( $tax_rate ) ) { + // Fetch the actual tax rate object for the order region & country. + $tax_rate_object = edd_get_tax_rate_by_location( array( + 'country' => $order_address_data['country'], + 'region' => $order_address_data['region'], + ) ); + + if ( ! empty( $tax_rate_object->id ) && $tax_rate_object->amount == $tax_rate ) { + $tax_rate_id = $tax_rate_object->id; + } + } + + /* + * If we cannot find a matching Adjustment object, we should save this in order meta so it isn't lost. + */ + if ( ! empty( $tax_rate ) && empty( $tax_rate_id ) ) { + $set_tax_rate_meta = true; + } + + // Build the order data before inserting. + $order_data = array( + 'id' => $data->ID, + 'parent' => $data->post_parent, + 'order_number' => $order_number, + 'status' => $order_status, + 'type' => 'sale', + 'date_created' => $date_created_gmt, // GMT is stored in the database as the offset is applied by the new query classes. + 'date_modified' => $data->post_modified_gmt, // GMT is stored in the database as the offset is applied by the new query classes. + 'date_completed' => $date_completed, + 'user_id' => $user_id, + 'customer_id' => $customer_id, + 'email' => $purchase_email, + 'ip' => $ip, + 'gateway' => $gateway, + 'mode' => $mode, + 'currency' => ! empty( $payment_meta['currency'] ) ? $payment_meta['currency'] : edd_get_currency(), + 'payment_key' => $purchase_key, + 'tax_rate_id' => $tax_rate_id, + 'subtotal' => $order_subtotal, + 'tax' => $order_tax, + 'discount' => $order_discount, + 'total' => $order_total, + ); + + // Orders placed prior to 2.8 won't have this meta, so only include it if the value isn't empty. + if ( ! empty( $date_actions_run ) ) { + $actions_date = new \EDD\Utils\Date(); + + // We store the dates in the DB as UTC. + $actions_date->setTimestamp( $date_actions_run )->setTimezone( new \DateTimeZone( 'UTC' ) ); + + // Format the date in MySQL DATETIME format. + $order_data['date_actions_run'] = $actions_date->format( 'mysql' ); + } + + /** + * Filters the data used to create the order. + * + * @since 3.0 + * + * @param array $order_data Order creation arguments. + * @param array $payment_meta Payment meta. + * @param array $cart_details Cart details. + * @param array $meta All payment meta. + */ + $order_data = apply_filters( 'edd_30_migration_order_creation_data', $order_data, $payment_meta, $cart_details, $meta ); + + update_option( '_edd_v30_doing_order_migration', true, false ); + + // Remove all order status transition actions. + remove_all_actions( 'edd_transition_order_status' ); + remove_all_actions( 'edd_transition_order_item_status' ); + + $order_id = edd_add_order( $order_data ); + + // Save an un-matched tax rate in order meta. + if ( $set_tax_rate_meta ) { + edd_add_order_meta( $order_id, 'tax_rate', $tax_rate ); + } + + // Do not pass the original order ID into other arrays + unset( $order_data['id'] ); + + // Reset the $refund_id variable so that we don't end up accidentally creating refunds. + $refund_id = 0; + + // If the order status is 'refunded', we need to generate a new order with the type of 'refund'. + if ( 'refunded' === $order_status ) { + + // Since the refund is a near copy of the original order, copy over the arguments. + $refund_data = $order_data; + + $refund_data['parent'] = $order_id; + $refund_data['order_number'] = $order_id . apply_filters( 'edd_order_refund_suffix', '-R-' ) . '1'; + $refund_data['type'] = 'refund'; + $refund_data['status'] = 'complete'; + + // Negate the amounts + $refund_data['subtotal'] = edd_negate_amount( $order_subtotal ); + $refund_data['tax'] = edd_negate_amount( $order_tax ); + $refund_data['discount'] = edd_negate_amount( $order_discount ); + $refund_data['total'] = edd_negate_amount( $order_total ); + + + // These are the best guess at the date it was refunded since we didn't store that prior. + $refund_data['date_created'] = $data->post_modified_gmt; + $refund_data['date_modified'] = $data->post_modified_gmt; + + $refund_id = edd_add_order( $refund_data ); + + } + + // Remove empty data. + $order_address_data = array_filter( $order_address_data ); + if ( ! empty( $order_address_data ) ) { + // Add to edd_order_addresses table. + $order_address_data['order_id'] = $order_id; + edd_add_order_address( $order_address_data ); + } + + // Maybe add the address to the edd_customer_addresses. + $customer_address_data = $order_address_data; + + // We don't need to pass this data to edd_maybe_add_customer_address(). + unset( $customer_address_data['order_id'] ); + unset( $customer_address_data['first_name'] ); + unset( $customer_address_data['last_name'] ); + + // If possible, set the order date as the address creation date. + $customer_address_data['date_created'] = $date_created_gmt; + + // Maybe add address to customer record. + edd_maybe_add_customer_address( $customer_id, $customer_address_data ); + + // Maybe add email address to customer record + if ( ! empty( $customer ) && $customer instanceof \EDD_Customer ) { + $type = ( $customer->email === $purchase_email ) ? 'primary' : 'secondary'; + edd_add_customer_email_address( + array( + 'customer_id' => $customer_id, + 'date_created' => $date_created_gmt, + 'email' => $purchase_email, + 'type' => $type, + ) + ); + } + + /** Migrate meta *********************************************/ + + // Unlimited downloads meta is not an order property, so we set it on the order meta for the new order ID. + if ( isset( $meta['_edd_payment_unlimited_downloads'] ) && ! empty( $meta['_edd_payment_unlimited_downloads'][0] ) ) { + edd_add_order_meta( $order_id, 'unlimited_downloads', $meta['_edd_payment_unlimited_downloads'][0] ); + } + + // Transaction IDs are no longer meta, and have their own table and data set, so we need to add the transactions. + $transaction_id = ! empty( $meta['_edd_payment_transaction_id'][0] ) ? $meta['_edd_payment_transaction_id'][0] : false; + // If we have no transaction ID & the gateway was PayPal, let's check in old payment notes. + if ( empty( $transaction_id ) && false !== strpos( $gateway, 'paypal' ) ) { + $transaction_id = self::find_transaction_id_from_notes( $order_id ); + } + if ( ! empty( $transaction_id ) ) { + edd_add_order_transaction( array( + 'object_id' => $order_id, + 'object_type' => 'order', + 'transaction_id' => $transaction_id, + 'gateway' => $gateway, + 'status' => 'complete', + 'total' => $order_total, + 'date_created' => $date_completed, + 'date_modified' => $date_completed, + ) ); + } + + /** + * By default, this is what is stored in payment meta. These array keys are part of the core payment meta in 2.x + * but are not needed as part of the order meta and will not be migrated. + * Extensions can add their keys to this filter if they use the payment meta array to store data and have + * established a migration process to keep the data intact with the new order tables. + * + * @since 3.0 + * @param array The array of payment meta keys. + */ + $core_meta_keys = apply_filters( 'edd_30_payment_meta_keys_not_migrated', array( + 'fees', + 'key', + 'email', + 'date', + 'downloads', + 'cart_details', + 'currency', + 'discount', + 'subtotal', + 'tax', + 'amount', + 'user_id', + ) ); + + // Remove core keys from `user_info`. + $remaining_user_info = false; + if ( ! empty( $user_info ) ) { + /** + * Array keys which are part of the core `user_info` in payment meta which are not needed as part of the order meta. + * Extensions can add their keys to this filter if they use the `user_info` array to store data and have + * established a migration process to keep the data intact with the new order tables. + * + * @since 3.0 + * @param array The array of user info keys. + */ + $core_user_info = apply_filters( 'edd_30_core_user_info', array( 'id', 'email', 'first_name', 'last_name', 'discount', 'address', 'user_id' ) ); + $remaining_user_info = array_diff_key( $user_info, array_flip( $core_user_info ) ); + } + + // If an extension has added data to `user_info`, migrate it. + if ( $remaining_user_info ) { + $payment_meta['user_info'] = $remaining_user_info; + } else { + $core_meta_keys[] = 'user_info'; + } + + // Remove all the core payment meta from the array, and... + if ( is_array( $payment_meta ) ) { + $remaining_payment_meta = array_diff_key( $payment_meta, array_flip( $core_meta_keys ) ); + + // ..If we have extra payment meta, it needs to be migrated across. + if ( 0 < count( $remaining_payment_meta ) ) { + edd_add_order_meta( $order_id, 'payment_meta', $remaining_payment_meta ); + } + } + + /** Create order items ***************************************/ + + // Now we iterate through all the cart items and make rows in the order items table. + if ( ! empty( $cart_details ) ) { + foreach ( $cart_details as $key => $cart_item ) { + // Reset any conditional IDs to be safe. + $refund_order_item_id = 0; + + // Get product name. + $product_name = isset( $cart_item['name'] ) + ? $cart_item['name'] + : ''; + + // Get price ID. + $price_id = self::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' => $order_id, + 'product_id' => $cart_item['id'], + 'product_name' => $product_name, + 'price_id' => $price_id, + 'cart_index' => $key, + 'type' => 'download', + 'status' => $order_status, + 'quantity' => $cart_item['quantity'], + 'amount' => (float) $cart_item['item_price'], + 'subtotal' => (float) $cart_item['subtotal'], + 'discount' => (float) $cart_item['discount'], + 'tax' => $cart_item['tax'], + 'total' => (float) $cart_item['price'], + 'date_created' => $date_created_gmt, + 'date_modified' => $data->post_modified_gmt, + ); + + /** + * Filters the arguments used to create the order item. + * + * @since 1.0 + * + * @param array $order_item_args Order item arguments. + * @param array $cart_item Original cart item. + * @param array $payment_meta Payment meta. + * @param array $meta All meta. + */ + $order_item_args = apply_filters( 'edd_30_migration_order_item_creation_data', $order_item_args, $cart_item, $payment_meta, $meta ); + + $order_item_id = edd_add_order_item( $order_item_args ); + + if ( ! empty( $cart_item['item_number']['options'] ) ) { + // Collect any item_number options and store them. + + // Remove our price_id and quantity, as they are columns on the order item now. + unset( $cart_item['item_number']['options']['price_id'] ); + unset( $cart_item['item_number']['options']['quantity'] ); + + foreach ( $cart_item['item_number']['options'] as $option_key => $value ) { + $option_key = '_option_' . sanitize_key( $option_key ); + + edd_add_order_item_meta( $order_item_id, $option_key, $value ); + } + } + + // If the order status is refunded, we also need to add all the refunded order items on the refund order as well. + if ( ! empty( $refund_id ) ) { + + // Since the refund is a near copy of the original order, copy over the arguments. + $refund_item_args = $order_item_args; + + $refund_item_args['parent'] = $order_item_id; + $refund_item_args['order_id'] = $refund_id; + $refund_item_args['status'] = 'complete'; + + // Subtotal is actually set to subtotal - discount. + $refund_item_args['subtotal'] = $refund_item_args['subtotal'] - $refund_item_args['discount']; + + // Negate the amounts + $refund_item_args['quantity'] = edd_negate_int( $cart_item['quantity'] ); + foreach( array( 'amount', 'subtotal', 'tax', 'total' ) as $field_to_negate ) { + $refund_item_args[ $field_to_negate ] = edd_negate_amount( $refund_item_args[ $field_to_negate ] ); + } + + // These are our best estimates since we did not store the refund date previously. + $refund_item_args['date_crated'] = $data->post_modified_gmt; + $refund_item_args['date_modified'] = $data->post_modified_gmt; + + $refund_order_item_id = edd_add_order_item( $refund_item_args ); + + if ( ! empty( $cart_item['item_number']['options'] ) ) { + // Collect any item_number options and store them. + + // Remove our price_id and quantity, as they are columns on the order item now. + unset( $cart_item['item_number']['options']['price_id'] ); + unset( $cart_item['item_number']['options']['quantity'] ); + + foreach ( $cart_item['item_number']['options'] as $option_key => $value ) { + $option_key = '_option_' . sanitize_key( $option_key ); + + edd_add_order_item_meta( $refund_order_item_id, $option_key, $value ); + } + } + + } + + // Store order item fees as adjustments. + if ( isset( $cart_item['fees'] ) && ! empty( $cart_item['fees'] ) ) { + foreach ( $cart_item['fees'] as $fee_id => $fee ) { + // Reset any conditional IDs to be safe. + $refund_adjustment_id = 0; + + $tax = EDD()->fees->get_calculated_tax( $fee, $tax_rate ); + $total = floatval( $fee['amount'] ) + $tax; + + // Track order item fees tax to adjust order if needed. + $order_items_fees_tax += $tax; + + // Add the adjustment. + $adjustment_args = array( + 'object_id' => $order_item_id, + 'object_type' => 'order_item', + 'type_key' => $fee_id, + 'type' => 'fee', + 'description' => $fee['label'], + 'subtotal' => floatval( $fee['amount'] ), + 'tax' => $tax, + 'total' => floatval( $fee['amount'] ) + $tax, + ); + + /** + * Filters the arguments used to create an order item adjustment. + * + * @since 3.0 + * + * @param array $adjustment_args Adjustment arguments for a fee. + * @param array $fee Original fee data. + * @param array $cart_item Cart item this fee is part of. + * @param array $payment_meta Payment meta. + * @param array $meta All meta. + */ + $adjustment_args = apply_filters( 'edd_30_migration_order_item_adjustment_creation_data', $adjustment_args, $fee, $cart_item, $payment_meta, $meta ); + + $adjustment_id = edd_add_order_adjustment( $adjustment_args ); + edd_add_extra_fee_order_adjustment_meta( $adjustment_id, $fee ); + + // If we refunded the main order, the fees also need to be added to the refund order type we created. + if ( ! empty( $refund_id ) ) { + $refund_adjustment_args = $adjustment_args; + $refund_adjustment_args['parent'] = $adjustment_id; + $refund_adjustment_args['object_id'] = $refund_order_item_id; + $refund_adjustment_args['subtotal'] = edd_negate_amount( floatval( $fee['amount'] ) ); + $refund_adjustment_args['tax'] = edd_negate_amount( $tax ); + $refund_adjustment_args['total'] = edd_negate_amount( floatval( $fee['amount'] ) + $tax ); + + $refund_adjustment_id = edd_add_order_adjustment( $refund_adjustment_args ); + } + } + } + } + + // Compatibility with older versions of EDD. + // Older versions stored a single dimensional array of download IDs. + } elseif ( is_array( $cart_downloads ) && count( $cart_downloads ) === count( $cart_downloads, COUNT_RECURSIVE ) ) { + foreach ( $cart_downloads as $cart_index => $download_id ) { + $download = edd_get_download( $download_id ); + + $order_item_args = array( + 'order_id' => $order_id, + 'product_id' => $download_id, + 'product_name' => $download->post_name, + 'price_id' => null, + 'cart_index' => $cart_index, + 'type' => 'download', + 'quantity' => 1, + 'amount' => (float) $payment_meta['amount'], + 'subtotal' => (float) $payment_meta['amount'], + 'discount' => 0.00, + 'tax' => 0.00, + 'total' => (float) $payment_meta['amount'], + 'date_created' => $date_created_gmt, + 'date_modified' => $data->post_modified_gmt, + ); + + $order_item_id = edd_add_order_item( $order_item_args ); + + // If the order was refunded, we also need to add these items to the refund order. + if ( ! empty( $refund_id ) ) { + + // Since the refund is a near copy of the original order, copy over the arguments. + $refund_item_args = $order_item_args; + + $refund_item_args['parent'] = $order_item_id; + $refund_item_args['order_id'] = $refund_id; + $refund_item_args['quantity'] = edd_negate_int( 1 ); + $refund_item_args['amount'] = edd_negate_amount( (float) $payment_meta['amount'] ); + $refund_item_args['subtotal'] = edd_negate_amount( (float) $payment_meta['amount'] ); + $refund_item_args['total'] = edd_negate_amount( (float) $payment_meta['amount'] ); + + // These are the best guess at the time, since we didn't store this data previously. + $refund_item_args['date_created'] = $data->post_modified_gmt; + $refund_item_args['date_modified'] = $data->post_modified_gmt; + + edd_add_order_item( $order_item_args ); + } + } + } + + /** Create order adjustments *********************************/ + + if ( isset( $payment_meta['fees'] ) && ! empty( $payment_meta['fees'] ) ) { + foreach ( $payment_meta['fees'] as $fee_id => $fee ) { + // Reset any conditional IDs to be safe. + $refund_adjustment_id = 0; + + if ( ! empty( $fee['download_id'] ) ) { + continue; + } + + $tax = EDD()->fees->get_calculated_tax( $fee, $tax_rate ); + $total = floatval( $fee['amount'] ) + $tax; + + $order_fees_tax += $tax; + $order_fees_total += $total; + + // Add the adjustment. + $adjustment_args = array( + 'object_id' => $order_id, + 'object_type' => 'order', + 'type_key' => $fee_id, + 'type' => 'fee', + 'description' => $fee['label'], + 'subtotal' => floatval( $fee['amount'] ), + 'tax' => $tax, + 'total' => $total, + 'date_created' => $date_created_gmt, + 'date_modified' => $data->post_modified_gmt, + ); + + /** + * Filters the order adjustment arguments. + * + * @since 3.0 + * + * @param array $adjustment_args Arguments used to create the order adjustment. + * @param array $fee Fee data. + * @param array $payment_meta Payment meta. + * @param array $meta All meta. + */ + $adjustment_args = apply_filters( 'edd_30_migration_order_adjustment_creation_data', $adjustment_args, $fee, $payment_meta, $meta ); + + $adjustment_id = edd_add_order_adjustment( $adjustment_args ); + edd_add_extra_fee_order_adjustment_meta( $adjustment_id, $fee ); + + if ( ! empty( $refund_id ) ) { + + // Since the refund is a near copy of the original order, copy over the arguments. + $refund_adjustment_args = $adjustment_args; + + $refund_adjustment_args['parent'] = $adjustment_id; + $refund_adjustment_args['object_id'] = $refund_id; + + // Negate the amounts. + $refund_adjustment_args['subtotal'] = edd_negate_amount( floatval( $fee['amount'] ) ); + $refund_adjustment_args['tax'] = edd_negate_amount( $tax ); + $refund_adjustment_args['total'] = edd_negate_amount( floatval( $fee['amount'] ) + $tax ); + + $refund_adjustment_id = edd_add_order_adjustment( $refund_adjustment_args ); + } + } + } + + // Add fee taxes (order and order item) if the order tax amount was previously manually calculated. + if ( false === $meta_tax ) { + edd_update_order( $order_id, array( + 'tax' => $order_tax + $order_fees_tax + $order_items_fees_tax, + ) ); + } + + // Add fee totals (order and order item) if the order tax amount was previously manually calculated. + // Order item fees were previously included in the total calculation. We must manually include + // order item fee tax amounts, and order fees total (subtotal + tax). + if ( false === $meta_total ) { + edd_update_order( $order_id, array( + 'total' => $order_total + $order_fees_total + $order_items_fees_tax, + ) ); + } + + // Insert discounts. + $discounts = ! empty( $user_info['discount'] ) + ? $user_info['discount'] + : array(); + + if ( ! is_array( $discounts ) ) { + $discounts = explode( ',', $discounts ); + } + + $first_discount = reset( $discounts ); + $discount_arg_defaults = array( + 'object_id' => $order_id, + 'object_type' => 'order', + 'type' => 'discount', + 'subtotal' => $order_discount, + 'total' => $order_discount, + 'date_created' => $date_created_gmt, + 'date_modified' => $data->post_modified_gmt, + ); + if ( ! empty( $discounts ) && ( 'none' !== $first_discount ) ) { + if ( 1 === count( $discounts ) ) { + $discount_code = reset( $discounts ); + + /** @var \EDD_Discount $discount_object */ + $discount_object = edd_get_discount_by( 'code', $discount_code ); + + if ( $discount_object instanceof \EDD_Discount ) { + $discount_args = wp_parse_args( + array( + 'type_id' => $discount_object->id, + 'description' => $discount_object->code, + ), + $discount_arg_defaults + ); + } else { + $discount_args = wp_parse_args( + array( + 'description' => $discount_code, + ), + $discount_arg_defaults + ); + } + + /** + * Filters the arguments used to create a discount adjustment. + * + * @since 3.0 + * + * @param array $discount_args Order adjustment arguments. + * @param \EDD_Discount $discount_object Discount object. + * @param float $order_subtotal Order subtotal. + * @param array $user_info User info array. + * @param array $payment_meta Payment meta. + * @param array $meta All post meta. + */ + $discount_args = apply_filters( 'edd_30_migration_order_discount_creation_data', $discount_args, $discount_object, $order_subtotal, $user_info, $payment_meta, $meta ); + + $new_discount_id = edd_add_order_adjustment( $discount_args ); + if ( $order_discount <= 0 ) { + edd_add_order_adjustment_meta( + $new_discount_id, + 'migrated_order_discount_unknown', + (int) $order_id, + true + ); + } + } else { + foreach ( $discounts as $discount_code ) { + + /** @var \EDD_Discount $discount_object */ + $discount_object = edd_get_discount_by( 'code', $discount_code ); + + if ( $discount_object instanceof \EDD_Discount ) { + $calculated_discount = $order_subtotal - $discount_object->get_discounted_amount( $order_subtotal ); + $discount_args = wp_parse_args( + array( + 'type_id' => $discount_object->id, + 'description' => $discount_object->code, + 'subtotal' => $calculated_discount, + 'total' => $calculated_discount, + ), + $discount_arg_defaults + ); + } else { + $discount_args = wp_parse_args( + array( + 'description' => $discount_code, + 'subtotal' => 0.00, + 'total' => 0.00, + ), + $discount_arg_defaults + ); + } + + /** + * Filters the arguments used to create a discount adjustment. + * + * @since 3.0 + * + * @param array $discount_args Order adjustment arguments. + * @param \EDD_Discount $discount_object Discount object. + * @param float $order_subtotal Order subtotal. + * @param array $user_info User info array. + * @param array $payment_meta Payment meta. + * @param array $meta All post meta. + */ + $discount_args = apply_filters( 'edd_30_migration_order_discount_creation_data', $discount_args, $discount_object, $order_subtotal, $user_info, $payment_meta, $meta ); + + $new_discount_id = edd_add_order_adjustment( $discount_args ); + if ( $calculated_discount <= 0 ) { + edd_add_order_adjustment_meta( + $new_discount_id, + 'migrated_order_discount_unknown', + (int) $order_id, + true + ); + } + } + } + } elseif ( ! empty( $discounts ) && 'none' === $first_discount && $order_discount > 0 ) { + // The order was saved with a discount amount, but no discount code. + $discount_args = wp_parse_args( + array( + 'description' => __( 'Legacy Discount', 'easy-digital-downloads' ), + ), + $discount_arg_defaults + ); + + // There is no filter applied here because there is no discount object. + edd_add_order_adjustment( $discount_args ); + } + + /** Create order meta ****************************************/ + + $core_meta_keys = array( + '_edd_payment_user_email', + '_edd_payment_customer_id', + '_edd_payment_user_id', + '_edd_payment_user_ip', + '_edd_payment_purchase_key', + '_edd_payment_total', + '_edd_payment_mode', + '_edd_payment_gateway', + '_edd_payment_meta', + '_edd_payment_tax', + '_edd_payment_tax_rate', + '_edd_completed_date', + '_edd_payment_unlimited_downloads', + '_edd_payment_number', + '_edd_payment_transaction_id', + '_edd_complete_actions_run', + ); + + // Determine what main payment meta keys were from core and what were custom... + $remaining_meta = array_diff_key( $meta, array_flip( $core_meta_keys ) ); + + // ...and whatever is not from core, needs to be added as new order meta. + foreach ( $remaining_meta as $meta_key => $meta_value ) { + $meta_value = maybe_unserialize( $meta_value[0] ); + + edd_add_order_meta( $order_id, $meta_key, $meta_value ); + } + + /** + * Now that we're done, let's run a hook here so we can allow extensions to make any necessary changes. + * + * @since 3.0 + * @param int $order_id The order ID. + * @param array $payment_meta The `_edd_payment_meta` value for the original payment. + * @param array $meta All post meta associated with the payment. + */ + do_action( 'edd_30_migrate_order', $order_id, $payment_meta, $meta ); + + delete_option( '_edd_v30_doing_order_migration' ); + + return $order_id; + } + + /** + * 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 + */ + protected static 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; + } + + // Return the price ID that's set to the cart item right now, if not numeric return NULL. + return 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; + } + + /** + * Attempts to locate a PayPal transaction ID from legacy payment notes. + * + * @since 3.0 + * + * @param int $payment_id + * + * @return string|false Transaction ID on success, false if not found. + */ + private static function find_transaction_id_from_notes( $payment_id ) { + global $wpdb; + + $payment_notes = $wpdb->get_col( $wpdb->prepare( + "SELECT comment_content FROM {$wpdb->comments} WHERE comment_post_ID = %d", + $payment_id + ) ); + + if ( empty( $payment_notes ) || ! is_array( $payment_notes ) ) { + return false; + } + + foreach ( $payment_notes as $note ) { + if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note, $match ) ) { + return $match[1]; + } + } + + return false; + } + + /** + * Tax rates. + * + * @since 3.0 + * + * @param object $data Data to migrate. + */ + public static function tax_rates( $data = null ) { + + // Bail if no data passed. + if ( ! $data ) { + return; + } + + $scope = ! empty( $data['global'] ) + ? 'country' + : 'region'; + + // If the scope is 'country', look for other active rates that are country wide and set them as 'inactive'. + if ( 'country' === $scope ) { + $tax_rates = edd_get_adjustments( + array( + 'type' => 'tax_rate', + 'status' => 'active', + 'scope' => 'country', + 'name' => $data['country'], + ) + ); + + if ( ! empty( $tax_rates ) ) { + foreach ( $tax_rates as $tax_rate ) { + edd_update_adjustment( + $tax_rate->id, + array( 'status' => 'inactive', ) + ); + } + } + } + + $adjustment_data = array( + 'name' => $data['country'], + 'scope' => $scope, + 'amount' => floatval( $data['rate'] ), + ); + + if ( ! empty( $data['state'] ) ) { + $adjustment_data['description'] = sanitize_text_field( $data['state'] ); + } + + edd_add_tax_rate( $adjustment_data ); + } + + /** + * Normalizes and backfills legacy payment cart data. + * + * @since 3.0.0 + * + * @param array|string $cart_details Cart details. No action is performed if a string + * (array cannot be unserialized) is provided. + * @return array|string + */ + private static function normalize_cart_details( $cart_details ) { + if ( ! is_array( $cart_details ) ) { + return $cart_details; + } + + foreach ( $cart_details as &$cart_item ) { + + // Get price. + $cart_item['price'] = isset( $cart_item['price'] ) + ? (float) $cart_item['price'] + : 0.00; + + // Get item price. + $cart_item['item_price'] = isset( $cart_item['item_price'] ) + ? (float) $cart_item['item_price'] + : (float) $cart_item['price']; + + // Get quantity. + $cart_item['quantity'] = isset( $cart_item['quantity'] ) + ? $cart_item['quantity'] + : 1; + + // Get subtotal. + $cart_item['subtotal'] = isset( $cart_item['subtotal'] ) + ? (float) $cart_item['subtotal'] + : (float) $cart_item['quantity'] * $cart_item['item_price']; + + // Get discount. + $cart_item['discount'] = isset( $cart_item['discount'] ) + ? (float) $cart_item['discount'] + : 0.00; + + // Get tax. + $cart_item['tax'] = isset( $cart_item['tax'] ) + ? (float) $cart_item['tax'] + : 0.00; + } + + return $cart_details; + } + + /** + * Given that some data quite possible has bad serialization, we need to possibly fix the bad serialization. + * + * @since 3.0.0 + * @param mixed $data The data to fix. + * @return mixed + */ + public static function fix_possible_serialization( $data ) { + return Serializer::fix_possible_serialization( $data ); + } +} diff --git a/src/Admin/Upgrades/v3/Discounts.php b/src/Admin/Upgrades/v3/Discounts.php new file mode 100644 index 00000000000..4461e9222ce --- /dev/null +++ b/src/Admin/Upgrades/v3/Discounts.php @@ -0,0 +1,96 @@ +completed_message = __( 'Discounts migration completed successfully.', 'easy-digital-downloads' ); + $this->upgrade = 'migrate_discounts'; + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + $offset = ( $this->step - 1 ) * $this->per_step; + + $results = $this->get_db()->get_results( $this->get_db()->prepare( + "SELECT * + FROM {$this->get_db()->posts} + WHERE post_type = %s + LIMIT %d, %d", + esc_sql( 'edd_discount' ), $offset, $this->per_step + ) ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + + // Check if discount has already been migrated. + if ( edd_get_discount( $result->ID ) ) { + continue; + } + + Data_Migrator::discounts( $result ); + } + + return true; + } + + return false; + } + + /** + * Calculate the percentage completed. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + $total = $this->get_db()->get_var( $this->get_db()->prepare( "SELECT COUNT(ID) AS count FROM {$this->get_db()->posts} WHERE post_type = %s", esc_sql( 'edd_discount' ) ) ); + + if ( empty( $total ) ) { + $total = 0; + } + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( $this->per_step * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } +} diff --git a/src/Admin/Upgrades/v3/Logs.php b/src/Admin/Upgrades/v3/Logs.php new file mode 100644 index 00000000000..7b2431e84d7 --- /dev/null +++ b/src/Admin/Upgrades/v3/Logs.php @@ -0,0 +1,94 @@ +completed_message = __( 'Logs migration completed successfully.', 'easy-digital-downloads' ); + $this->upgrade = 'migrate_logs'; + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + $offset = ( $this->step - 1 ) * $this->per_step; + + $results = $this->get_db()->get_results( $this->get_db()->prepare( + "SELECT p.*, t.slug + FROM {$this->get_db()->posts} AS p + LEFT JOIN {$this->get_db()->term_relationships} AS tr ON (p.ID = tr.object_id) + LEFT JOIN {$this->get_db()->term_taxonomy} AS tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) + LEFT JOIN {$this->get_db()->terms} AS t ON (tt.term_id = t.term_id) + WHERE p.post_type = %s AND t.slug != %s + GROUP BY p.ID + LIMIT %d, %d", + esc_sql( 'edd_log' ), esc_sql( 'sale' ), $offset, $this->per_step + ) ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + Data_Migrator::logs( $result ); + } + + return true; + } + + return false; + } + + /** + * Calculate the percentage completed. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + $total = $this->get_db()->get_var( $this->get_db()->prepare( "SELECT COUNT(ID) AS count FROM {$this->get_db()->posts} WHERE post_type = %s", esc_sql( 'edd_log' ) ) ); + + if ( empty( $total ) ) { + $total = 0; + } + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( $this->per_step * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } +} diff --git a/src/Admin/Upgrades/v3/Order_Notes.php b/src/Admin/Upgrades/v3/Order_Notes.php new file mode 100644 index 00000000000..c096de3f95a --- /dev/null +++ b/src/Admin/Upgrades/v3/Order_Notes.php @@ -0,0 +1,92 @@ +completed_message = __( 'Order notes migration completed successfully.', 'easy-digital-downloads' ); + $this->upgrade = 'migrate_order_notes'; + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + $offset = ( $this->step - 1 ) * $this->per_step; + + $results = $this->get_db()->get_results( $this->get_db()->prepare( + "SELECT * + FROM {$this->get_db()->comments} + WHERE comment_type = %s + ORDER BY comment_id ASC + LIMIT %d, %d", + esc_sql( 'edd_payment_note' ), $offset, $this->per_step + ) ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + $result->object_id = $result->comment_post_ID; + Data_Migrator::order_notes( $result ); + } + + return true; + } + + return false; + } + + /** + * Calculate the percentage completed. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + $total = $this->get_db()->get_var( $this->get_db()->prepare( "SELECT COUNT(comment_ID) AS count FROM {$this->get_db()->comments} WHERE comment_type = %s", esc_sql( 'edd_payment_note' ) ) ); + + if ( empty( $total ) ) { + $total = 0; + } + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( $this->per_step * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } +} diff --git a/src/Admin/Upgrades/v3/Orders.php b/src/Admin/Upgrades/v3/Orders.php new file mode 100644 index 00000000000..6ae33f7bf60 --- /dev/null +++ b/src/Admin/Upgrades/v3/Orders.php @@ -0,0 +1,181 @@ +completed_message = __( 'Orders migration completed successfully.', 'easy-digital-downloads' ); + $this->upgrade = array( 'migrate_orders', 'migrate_order_actions_date' ); + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + $this->maybe_reset_auto_increment(); + $offset = ( $this->step - 1 ) * $this->per_step; + + $results = $this->get_db()->get_results( + $this->get_db()->prepare( + "SELECT * + FROM {$this->get_db()->posts} + WHERE post_type = %s + ORDER BY ID ASC + LIMIT %d, %d", + esc_sql( 'edd_payment' ), + $offset, + $this->per_step + ) + ); + + if ( ! empty( $results ) ) { + $orders = new \EDD\Database\Queries\Order(); + foreach ( $results as $result ) { + + // Check if order has already been migrated. + if ( $orders->get_item( $result->ID ) ) { + continue; + } + + Data_Migrator::orders( $result ); + } + + return true; + } + $this->recalculate_sales_earnings(); + + return false; + } + + /** + * Recalculates the sales and earnings numbers for all downloads once the orders have been migrated. + * + * @since 3.0 + * @return void + */ + private function recalculate_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 ) ) { + foreach ( $downloads as $download ) { + edd_recalculate_download_sales_earnings( $download->ID ); + } + } + } + + /** + * Recalculates all customer values. + * + * @since 3.1.2 + * @return void + */ + private function recalculate_customer_values() { + $customers = edd_get_customers( + array( + 'number' => 9999999, + ) + ); + + if ( ! empty( $customers ) ) { + foreach ( $customers as $customer ) { + $customer->recalculate_stats(); + } + } + } + + /** + * Calculate the percentage completed. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + $total = $this->get_db()->get_var( $this->get_db()->prepare( "SELECT COUNT(id) AS count FROM {$this->get_db()->posts} WHERE post_type = %s", esc_sql( 'edd_payment' ) ) ); + + if ( empty( $total ) ) { + $total = 0; + } + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( $this->per_step * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Maybe reset the auto increment for the orders table. + * + * @since 3.3.0 + * @return void + */ + private function maybe_reset_auto_increment() { + if ( $this->step > 1 ) { + return; + } + + $last_payment = $this->get_db()->get_var( + $this->get_db()->prepare( + "SELECT ID + FROM {$this->get_db()->posts} + WHERE post_type = %s + ORDER BY ID DESC + LIMIT 1", + esc_sql( 'edd_payment' ) + ) + ); + + if ( empty( $last_payment ) ) { + return; + } + + // If the last payment is less than the current auto increment, we need to reset the auto increment to the last payment + 1. + $auto_increment = $this->get_db()->get_var( "SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" . DB_NAME . "' AND TABLE_NAME = '" . $this->get_db()->prefix . "edd_orders'" ); + if ( $auto_increment < $last_payment ) { + $new_auto_increment = $last_payment + 1; + $this->get_db()->query( "ALTER TABLE " . $this->get_db()->prefix . "edd_orders AUTO_INCREMENT = " . $new_auto_increment ); + } + } +} diff --git a/src/Admin/Upgrades/v3/Remove_Legacy_Data.php b/src/Admin/Upgrades/v3/Remove_Legacy_Data.php new file mode 100644 index 00000000000..5c9f820db03 --- /dev/null +++ b/src/Admin/Upgrades/v3/Remove_Legacy_Data.php @@ -0,0 +1,167 @@ +completed_message = __( 'Legacy data removed successfully.', 'easy-digital-downloads' ); + $this->upgrade = 'v30_legacy_data_removed'; + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + // Perform some database operations on the first step. + if ( 1 === $this->step ) { + // Drop customer `payment_ids` column. It's no longer needed. + $customer_table = edd_get_component_interface( 'customer', 'table' ); + if ( $customer_table instanceof \EDD\Database\Tables\Customers ) { + if ( $customer_table->column_exists( 'payment_ids' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->get_db()->edd_customers} DROP `payment_ids`" ); + } + + if ( $customer_table->column_exists( 'notes' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->get_db()->edd_customers} DROP `notes`" ); + } + } + + // Delete unneeded meta. + $this->get_db()->query( $this->get_db()->prepare( "DELETE FROM {$this->get_db()->edd_customermeta} WHERE meta_key = %s", esc_sql( 'additional_email' ) ) ); + $this->get_db()->query( $this->get_db()->prepare( "DELETE FROM {$this->get_db()->usermeta} WHERE meta_key = %s", esc_sql( '_edd_user_address' ) ) ); + } + + // First delete custom post types. + $results = $this->get_db()->get_col( $this->get_db()->prepare( + "SELECT id + FROM {$this->get_db()->posts} + WHERE post_type IN(%s, %s, %s) + ORDER BY id ASC + LIMIT %d", + esc_sql( 'edd_payment' ), esc_sql( 'edd_discount' ), esc_sql( 'edd_log' ), $this->per_step + ), 0 ); + + $data_was_deleted = false; + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + wp_delete_post( $result, true ); + } + + $data_was_deleted = true; + } + + // Then delete order notes, stored in comments. + $results = $this->get_db()->get_col( $this->get_db()->prepare( + "SELECT comment_ID + FROM {$this->get_db()->comments} + WHERE comment_type = %s + ORDER BY comment_ID ASC + LIMIT %d", + 'edd_payment_note', $this->per_step + ) ); + if ( ! empty( $results ) ) { + foreach( $results as $result ) { + wp_delete_comment( $result, true ); + } + + $data_was_deleted = true; + } + + return $data_was_deleted; + } + + /** + * Calculate the percentage completed. + * + * Because we're *deleting* records as we go, this percentage will not be accurate because we don't track + * exactly how many we've deleted. So this percentage is really just best guess. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + // Get post type total. + $total = $this->get_db()->get_var( $this->get_db()->prepare( + "SELECT COUNT(id) AS count + FROM {$this->get_db()->posts} + WHERE post_type IN(%s, %s, %s)", + esc_sql( 'edd_payment' ), esc_sql( 'edd_discount' ), esc_sql( 'edd_log' ) + ) ); + + if ( empty( $total ) ) { + $total = 0; + } + + // Get order note total. + $order_note_total = $this->get_db()->get_var( $this->get_db()->prepare( + "SELECT COUNT(comment_ID) AS count + FROM {$this->get_db()->comments} + WHERE comment_type = %s", + 'edd_payment_note' + ) ); + + if ( empty( $order_note_total ) ) { + $order_note_total = 0; + } + + // Combine the two. + $total += $order_note_total; + + // Estimate how many we've already done to improve the percentage. + $number_done = $this->per_step * $this->step; + $total += $number_done; + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( $number_done / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } +} diff --git a/src/Admin/Upgrades/v3/Tax_Rates.php b/src/Admin/Upgrades/v3/Tax_Rates.php new file mode 100644 index 00000000000..78d12684a7d --- /dev/null +++ b/src/Admin/Upgrades/v3/Tax_Rates.php @@ -0,0 +1,97 @@ +completed_message = __( 'Tax rates migration completed successfully.', 'easy-digital-downloads' ); + $this->upgrade = 'migrate_tax_rates'; + } + + /** + * Retrieve the data pertaining to the current step and migrate as necessary. + * + * @since 3.0 + * + * @return bool True if data was migrated, false otherwise. + */ + public function get_data() { + $offset = ( $this->step - 1 ) * $this->per_step; + + if ( 1 === $this->step ) { + $default_tax_rate = edd_get_option( 'tax_rate', false ); + if ( ! empty( $default_tax_rate ) ) { + edd_add_tax_rate( + array( + 'scope' => 'global', + 'amount' => floatval( $default_tax_rate ), + ) + ); + } + } + + $results = get_option( 'edd_tax_rates', array() ); + $results = array_slice( $results, $offset, $this->per_step, true ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + Data_Migrator::tax_rates( $result ); + } + + return true; + } + + return false; + } + + /** + * Calculate the percentage completed. + * + * @since 3.0 + * + * @return float Percentage. + */ + public function get_percentage_complete() { + $total = count( get_option( 'edd_tax_rates', array() ) ); + + if ( empty( $total ) ) { + $total = 0; + } + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( $this->per_step * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } +} diff --git a/src/Assets/Checkout.php b/src/Assets/Checkout.php new file mode 100644 index 00000000000..6425594c24d --- /dev/null +++ b/src/Assets/Checkout.php @@ -0,0 +1,59 @@ + 'vendor/jquery.creditcardvalidator.min.js', + 'jQuery.payment' => 'vendor/jquery.payment.min.js', + 'edd-checkout-global' => 'edd-checkout-global.js', + 'edd-ajax' => 'edd-ajax.js', + ); + foreach ( $scripts as $handle => $file ) { + wp_register_script( + $handle, + EDD_PLUGIN_URL . 'assets/js/' . $file, + array( 'jquery' ), + edd_admin_get_script_version(), + edd_scripts_in_footer() + ); + } + } + + /** + * Enqueue scripts for the checkout page. + * + * @since 3.3.0 + * @return void + */ + public static function enqueue() { + // Enqueue credit-card validator. + if ( edd_is_cc_verify_enabled() ) { + wp_enqueue_script( 'creditCardValidator' ); + } + + // Enqueue global checkout. + wp_enqueue_script( 'edd-checkout-global' ); + } +} diff --git a/src/Assets/Localization.php b/src/Assets/Localization.php new file mode 100644 index 00000000000..3d9f81fdeee --- /dev/null +++ b/src/Assets/Localization.php @@ -0,0 +1,141 @@ +ID ) + ? edd_get_item_position_in_cart( $post->ID ) + : -1; + + 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' => self::has_purchase_links() ? '1' : '0', + '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(), + ) + ) + ); + } + + /** + * Gets the localization variables for the checkout page. + * + * @since 3.3.0 + * @return array + */ + private static function get_checkout_variables() { + $currency = new \EDD\Currency\Currency( edd_get_currency() ); + + return 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' => edd_admin_get_script_version(), + 'current_page' => get_the_ID(), + 'showStoreErrors' => current_user_can( 'manage_shop_settings' ) ? 'true' : 'false', + ) + ); + } + + /** + * Check if the current page has purchase links. + * + * @since 3.3.0 + * @return bool + */ + private static function has_purchase_links() { + if ( is_post_type_archive( 'download' ) ) { + return true; + } + + $post_id = get_the_ID(); + if ( has_block( 'edd/downloads', $post_id ) || has_block( 'edd/buy-button', $post_id ) ) { + return true; + } + + global $post; + if ( ! empty( $post->post_content ) && + ( + has_shortcode( $post->post_content, 'purchase_link' ) || + has_shortcode( $post->post_content, 'downloads' ) + ) + ) { + return true; + } + + return false; + } +} + diff --git a/src/Assets/Styles.php b/src/Assets/Styles.php new file mode 100644 index 00000000000..da857132eb1 --- /dev/null +++ b/src/Assets/Styles.php @@ -0,0 +1,143 @@ + + + post_content, 'downloads' ) ) { + return false; + } + + // 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 ( + FileSystem::file_exists( $child_theme_style_sheet ) || + FileSystem::file_exists( $child_theme_style_sheet_2 ) || + FileSystem::file_exists( $parent_theme_style_sheet ) || + FileSystem::file_exists( $parent_theme_style_sheet_2 ) + ) { + return apply_filters( 'edd_load_head_styles', true ); + } + + return false; + } +} diff --git a/src/CLI/Migration/CustomerEmails.php b/src/CLI/Migration/CustomerEmails.php new file mode 100644 index 00000000000..d661ba49e23 --- /dev/null +++ b/src/CLI/Migration/CustomerEmails.php @@ -0,0 +1,80 @@ +edd_customers} + WHERE email != '' + AND email NOT IN ( + SELECT email + FROM {$wpdb->edd_customer_email_addresses} + )"; + $sql = $sql_base . ' LIMIT 1'; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + + if ( ! $has_results ) { + \WP_CLI::line( __( 'No customers with missing emails were found.', 'easy-digital-downloads' ) ); + return; + } + + $total = count( $wpdb->get_results( $sql_base ) ); + $progress = new \cli\progress\Bar( 'Adding Missing Customer Emails', $total ); + $progress->tick(); + + $count = 0; + while ( $has_results ) { + $progress->tick(); + + // Query & count. + $sql = "{$sql_base} LIMIT 50"; + $results = $wpdb->get_results( $sql ); + + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + $customer_has_primary = edd_count_customer_email_addresses( + array( + 'customer_id' => $result->id, + 'type' => 'primary', + ) + ); + edd_add_customer_email_address( + array( + 'customer_id' => $result->id, + 'email' => $result->email, + 'date_created' => $result->date_created, + 'type' => $customer_has_primary ? 'secondary' : 'primary', + ) + ); + + // Tick the spinner... + $progress->tick(); + ++$count; + } + } else { + $has_results = false; + } + } + + $progress->finish(); + \WP_CLI::line( __( 'Missing Customer Emails Added:', 'easy-digital-downloads' ) . $count ); + } +} diff --git a/src/CLI/Migration/Discounts.php b/src/CLI/Migration/Discounts.php new file mode 100644 index 00000000000..e166b82f5fc --- /dev/null +++ b/src/CLI/Migration/Discounts.php @@ -0,0 +1,78 @@ +edd_orders} o + WHERE o.type = 'sale' + AND o.status IN( 'complete', 'edd_subscription', 'refunded', 'partially_refunded' ) + AND o.discount > 0 + AND NOT EXISTS( + SELECT * + FROM {$wpdb->edd_order_adjustments} oa + WHERE oa.type = 'discount' + AND oa.object_id = o.id + )"; + + // Query & count. + $sql = "{$sql_base} LIMIT 1"; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + $number = 50; + $step = 0; + + if ( ! $has_results ) { + \WP_CLI::line( __( 'No orders with missing discounts were found.', 'easy-digital-downloads' ) ); + return; + } + + $total = count( $wpdb->get_results( $sql_base ) ); + $progress = new \cli\progress\Bar( 'Adding Missing Discounts', $total ); + $progress->tick(); + + while ( $has_results ) { + $sql = $sql_base . " LIMIT {$number}"; + $results = $wpdb->get_results( $sql ); + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + $discount = edd_add_order_adjustment( + array( + 'object_id' => $result->id, + 'object_type' => 'order', + 'type' => 'discount', + 'description' => __( 'Legacy Discount', 'easy-digital-downloads' ), + 'subtotal' => $result->discount, + 'total' => $result->discount, + 'rate' => $result->rate, + ) + ); + } + + $step++; + $progress->tick(); + } else { + $has_results = false; + $progress->finish(); + } + } + + \WP_CLI::line( __( 'Missing Discounts Added:', 'easy-digital-downloads' ) . $total ); + } +} diff --git a/src/Checkout/AutoRegister.php b/src/Checkout/AutoRegister.php new file mode 100644 index 00000000000..89c2621860d --- /dev/null +++ b/src/Checkout/AutoRegister.php @@ -0,0 +1,456 @@ + 'create_user_and_add_to_order', + 'edd_free_downloads_post_complete_payment' => 'create_user_and_add_to_order', + 'edd_post_add_manual_order' => array( 'insert_user_during_manual_order', 10, 3 ), + 'edd_get_option_show_register_form' => 'remove_register_form', + 'edd_settings_marketing' => array( 'update_free_downloads_settings', 100 ), + 'edd_batch_import_order_created' => 'create_user_during_import', + ); + } + + /** + * Determines if the auto registration is enabled. + * + * @since 3.3.0 + * @return bool Returns true if the method can be executed, false otherwise. + */ + public static function is_enabled() { + if ( is_null( self::$is_enabled ) || edd_is_doing_unit_tests() ) { + self::$is_enabled = 'auto' === edd_get_option( 'logged_in_only', false ); + } + + return self::$is_enabled; + } + + /** + * Hide the registration form on checkout. + * This is a legacy method from the original Auto Register plugin. + * + * @since 3.3.0 + * @param string $value The value of the `show_register_form` option. + */ + public function remove_register_form( $value ) { + if ( ! self::is_enabled() ) { + return $value; + } + + if ( 'both' === $value ) { + return 'login'; + } + if ( 'registration' === $value ) { + return 'none'; + } + + return $value; + } + + /** + * Maybe registers the user. + * Legacy method from the original Auto Register plugin. + * + * @since 3.3.0 + * @param int $order_id The order ID. + * @return void + */ + public function create_user_and_add_to_order( $order_id ) { + if ( ! self::is_enabled() ) { + return; + } + if ( is_user_logged_in() ) { + return; + } + + $order = edd_get_order( $order_id ); + if ( empty( $order->email ) ) { + return; + } + + $this->maybe_remove_user_registration_actions( $order->email, $order_id ); + + $user_id = $this->maybe_create_user( $order_id ); + if ( $user_id ) { + $this->assign_user_to_order( $user_id, $order_id ); + } + } + + /** + * Updates the settings for Free Downloads. + * + * @since 3.3.0 + * @param array $settings The settings array. + * @return array + */ + public function update_free_downloads_settings( $settings ) { + if ( ! function_exists( 'edd_free_downloads' ) ) { + return $settings; + } + if ( ! self::is_enabled() ) { + return $settings; + } + if ( empty( $settings['free_downloads'] ) ) { + return $settings; + } + foreach ( $settings['free_downloads'] as $key => $setting ) { + if ( ! empty( $setting['id'] ) && 'edd_free_downloads_bypass_auto_register' === $setting['id'] ) { + $settings['free_downloads'][ $key ]['tooltip_desc'] = __( 'Your site registers a new user account when an order is placed. You may allow free downloads without account creation.', 'easy-digital-downloads' ); + } + } + + return $settings; + } + + /** + * Adds a new user when an order is manually created in EDD 3.0. + * This is a legacy method from the original Auto Register plugin. + * + * @since 3.3.0 + * @param int $order_id The order ID. + * @param array $order_data The array of order data. + * @param array $args The original form data. + * @return void + */ + public function insert_user_during_manual_order( $order_id, $order_data, $args ) { + if ( empty( $args['edd-new-customer'] ) ) { + return; + } + if ( ! self::is_enabled() ) { + return; + } + + $user_id = $this->maybe_create_user( $order_id ); + if ( $user_id ) { + $this->assign_user_to_order( $user_id, $order_id ); + } + } + + /** + * Creates a user account during payment import. + * This is a legacy method from the original Auto Register plugin. + * + * @since 3.3.0 + * @param int $order_id The order ID. + * @return void + */ + public function create_user_during_import( $order_id ) { + if ( ! self::is_enabled() ) { + return; + } + + remove_action( 'edd_customer_post_attach_payment', 'edd_connect_guest_customer_to_existing_user' ); + remove_action( 'edd_insert_user', 'edd_new_user_notification' ); + remove_action( 'user_register', 'edd_add_past_purchases_to_new_user' ); + add_filter( 'edd_auto_register_login_user', '__return_false' ); + + $this->maybe_create_user( $order_id ); + } + + /** + * Processes the supplied payment data to possibly register a user + * + * @since 3.3.0 + * @param int $order_id The order ID. + * @param array $purchase_data The purchase data. + * @return int|false The User ID created false if the insert fails. + */ + protected function maybe_create_user( $order_id = 0, $purchase_data = array() ) { + if ( ! self::is_enabled() ) { + return false; + } + $order_data = $this->get_order_data( $order_id, $purchase_data ); + if ( ! $this->can_create_user( $order_data ) ) { + return false; + } + + /** + * Allow developers to modify the user data before it's inserted. + * + * @since 3.3.0 + * @param array $user_data The user data. + * @param int $order_id The order ID. + * @param array $order_data The order data. Note that this is different than the $purchase_data array. + */ + $user_data = apply_filters( + 'edd_auto_register_insert_user_args', + array( + 'user_login' => $order_data['user_name'], + 'user_pass' => wp_generate_password( 32 ), + 'user_email' => $order_data['email'], + 'first_name' => $order_data['first_name'], + 'last_name' => $order_data['last_name'], + ), + $order_id, + $order_data + ); + + // Insert new user. + if ( $this->should_log_user_in() ) { + $user_id = edd_register_and_login_new_user( $user_data ); + } else { + $user_id = wp_insert_user( $user_data ); + if ( ! is_wp_error( $user_id ) ) { + do_action( 'edd_insert_user', $user_id, $user_data ); + } + } + + // Depending on how the user was added, the user ID may be a WP_Error object, empty, or -1. + if ( is_wp_error( $user_id ) || empty( $user_id ) || $user_id < 0 ) { + return false; + } + + $customer = edd_get_customer_by( 'email', $order_data['email'] ); + if ( $customer ) { + $customer->update( array( 'user_id' => $user_id ) ); + } + + return $user_id; + } + + /** + * Checks if a user already exists during checkout. + * + * @since 3.3.0 + * @deprecated 3.3.5 for EDD\Checkout\Errors->check_existing_users(). + * @param mixed $user The user object. + * @param array $valid_data The valid data. + * @param array $posted The posted data. + * @return void + */ + public function check_existing_user( $user, $valid_data, $posted ) { + $errors = new \EDD\Checkout\Errors(); + $errors->check_existing_users( $user, $valid_data, $posted ); + } + + /** + * Whether a new user account can be created for an order. + * + * @since 3.3.0 + * @param array $data The order data to use for validation. + * @return bool + */ + private function can_create_user( $data ) { + + if ( empty( $data['user_name'] ) || ! self::is_enabled() ) { + return false; + } + + // If there is already a user ID assigned to the order, do not create a new user. + if ( ! empty( $data['order']->user_id ) ) { + return false; + } + + if ( ! empty( $data['email'] ) ) { + $user = get_user_by( 'email', $data['email'] ); + // User account already exists. + if ( $user instanceof \WP_User ) { + // For multisite, associate the user with the site. + if ( is_multisite() ) { + add_user_to_blog( get_current_blog_id(), $user->ID, get_option( 'default_role' ) ); + } + return false; + } + } + + // Username already exists. + if ( username_exists( $data['user_name'] ) ) { + return false; + } + + /** + * Allow developers to modify whether a user can be created. + * + * @since 3.3.0 + * @param bool Whether the user can be registered. + * @param \EDD\Orders\Order $order The order object. + * @param string $user_name The new user name that's been checked. + */ + $can_create_user = apply_filters( 'edd_can_create_user_for_order', true, $data['order'], $data['user_name'] ); + if ( has_filter( 'edd_auto_register_can_create_user' ) ) { + _deprecated_hook( 'edd_auto_register_can_create_user', '3.3.0', 'edd_can_create_user_for_order' ); + $can_create_user = apply_filters( 'edd_auto_register_can_create_user', $can_create_user, edd_get_payment( $data['order']->id ), $data['user_name'] ); + } + + return $can_create_user; + } + + /** + * Whether the user should be logged in after registration. + * + * @since 3.3.0 + * @return bool + */ + private function should_log_user_in() { + if ( is_user_logged_in() || ! self::is_enabled() ) { + return false; + } + $maybe_login_user = false; + if ( did_action( 'edd_purchase' ) || did_action( 'edd_straight_to_gateway' ) || did_action( 'edd_free_download_process' ) || edd_get_purchase_session() ) { + $maybe_login_user = true; + } + + return apply_filters( 'edd_auto_register_login_user', $maybe_login_user ); + } + + /** + * Assigns a user to an order. + * + * @since 3.3.0 + * @param int $user_id The user ID. + * @param int $order_id The order ID. + * @return bool + */ + private function assign_user_to_order( $user_id, $order_id ) { + return edd_update_order( + $order_id, + array( + 'user_id' => $user_id, + ) + ); + } + + /** + * Removes EDD core's user registration actions for the user's initial purchase. + * + * @since 3.3.0 + * @param string $email The customer's email address. + * @param int $order_id The current order ID. + * @return void + */ + private function maybe_remove_user_registration_actions( $email, $order_id ) { + + $customer = edd_get_customer_by( 'email', $email ); + if ( ! $customer ) { + return; + } + $orders = edd_count_orders( + array( + 'customer_id' => $customer->id, + 'type' => 'sale', + 'id__not_in' => array( $order_id ), + 'status__in' => edd_get_complete_order_statuses(), + ) + ); + + // If the new order is the only order, remove the actions that would otherwise create a new user. + if ( empty( $orders ) ) { + remove_action( 'user_register', 'edd_connect_existing_customer_to_new_user' ); + remove_action( 'user_register', 'edd_add_past_purchases_to_new_user' ); + } + } + + /** + * Gets the order data for a given order ID or purchase data to validate user creation. + * + * @since 3.3.0 + * @param int $order_id The order ID. + * @param array $purchase_data The purchase data. + * @return array|bool + */ + private function get_order_data( $order_id, $purchase_data = array() ) { + $order_data = array( + 'order' => false, + 'email' => '', + 'user_name' => '', + 'first_name' => '', + 'last_name' => '', + ); + if ( ! empty( $order_id ) ) { + $order = edd_get_order( $order_id ); + if ( empty( $order->customer_id ) ) { + return false; + } + $order_data['order'] = $order; + $order_data['email'] = $order->email; + $order_data['user_name'] = sanitize_user( $order->email ); + $customer = edd_get_customer( $order->customer_id ); + if ( ! empty( $customer->name ) ) { + $names = explode( ' ', $customer->name ); + $order_data['first_name'] = array_shift( $names ); + $order_data['last_name'] = implode( ' ', $names ); + } + + return $order_data; + } + + if ( ! empty( $purchase_data['user_info']['email'] ) ) { + $order_data['email'] = $purchase_data['user_info']['email']; + $order_data['user_name'] = sanitize_user( $purchase_data['user_info']['email'] ); + } + if ( ! empty( $purchase_data['user_info']['first_name'] ) ) { + $order_data['first_name'] = $purchase_data['user_info']['first_name']; + } + if ( ! empty( $purchase_data['user_info']['last_name'] ) ) { + $order_data['last_name'] = $purchase_data['user_info']['last_name']; + } + + return $order_data; + } + + /** + * Checks if a user should be inserted and inserts the user if necessary. + * This is a legacy method from the original Auto Register plugin. + * + * @deprecated 3.3.0 + * @param int $payment_id The ID of the payment. + * @param bool $payment Optional. The payment object. Default is false. + */ + public function maybe_insert_user( $payment_id = 0, $payment = false ) { + _edd_deprecated_function( __METHOD__, '3.3.0', 'EDD\\Checkout\\AutoRegister::create_user_during_import' ); + } + + /** + * Creates the user if necessary. + * If the order ID is provided, the user is created and assigned to the order. + * This method is needed for creating a user before the order is created (eg when purchasing + * a subscription with PayPal). + * This is a legacy method from the original Auto Register plugin. + * + * @since 3.3.0 + * @param array $purchase_data The purchase data. Used when creating a user before the order is created. + * @param int $order_id The order ID. + * @return int|bool The user ID if the user was created, false otherwise. + */ + public function create_user( $purchase_data = array(), $order_id = 0 ) { + if ( ! self::is_enabled() ) { + return false; + } + if ( is_user_logged_in() ) { + return false; + } + if ( ! empty( $order_id ) ) { + return $this->maybe_create_user( $order_id ); + } + + return $this->maybe_create_user( 0, $purchase_data ); + } +} diff --git a/src/Checkout/Errors.php b/src/Checkout/Errors.php new file mode 100644 index 00000000000..923bbcda603 --- /dev/null +++ b/src/Checkout/Errors.php @@ -0,0 +1,185 @@ + array( 'check_existing_users', 10, 3 ), + 'wp_ajax_nopriv_edd_check_email' => 'check_email_ajax', + 'wp_ajax_edd_check_email' => 'check_email_ajax', + ); + } + + /** + * Checks if a user already exists during checkout. + * + * @since 3.3.5 + * @param mixed $user The user object. + * @param array $valid_data The valid data. + * @param array $posted The posted data. + * @return void + */ + public function check_existing_users( $user, $valid_data, $posted ) { + if ( is_user_logged_in() || empty( $user ) ) { + return; + } + + // If the email has already been validated, skip this check. + if ( EDD()->session->get( 'email_validated' ) ) { + return; + } + + $email = false; + if ( ! empty( $valid_data['guest_user_data']['user_email'] ) ) { + $email = $valid_data['guest_user_data']['user_email']; + } elseif ( ! empty( $posted['edd_email'] ) ) { + $email = $posted['edd_email']; + } + if ( ! $email ) { + return; + } + + $validate = $this->validate_email( $email ); + if ( is_wp_error( $validate ) ) { + edd_set_error( $validate->get_error_code(), $validate->get_error_message() ); + } + } + + /** + * Checks if the email is valid and not already used. + * + * @since 3.3.5 + * @return void|bool|\WP_Error + */ + public function check_email_ajax() { + EDD()->session->set( 'email_validated', null ); + $email = sanitize_email( $_POST['email'] ); + if ( is_user_logged_in() ) { + $validate = $this->validate_logged_in_email( $email ); + } else { + $validate = $this->validate_email( $email ); + } + + if ( edd_is_doing_unit_tests() ) { + return $validate; + } + + if ( is_wp_error( $validate ) ) { + wp_send_json_error( array( 'message' => $validate->get_error_message() ) ); + } + + EDD()->session->set( 'email_validated', $email ); + + wp_send_json_success(); + } + + /** + * Validates an email address. + * + * @since 3.3.5 + * @param string $email The email address to validate. + * @return bool|\WP_Error + */ + private function validate_email( $email ) { + if ( ! is_email( $email ) ) { + return new \WP_Error( 'invalid_email', __( 'Please enter a valid email address.', 'easy-digital-downloads' ) ); + } + + // If there is no user with this email, it's valid. + $user = get_user_by( 'email', $email ); + if ( ! $user ) { + return true; + } + + // If there isn't a customer with this email, and guest checkout is enabled, it's valid. + $customer = edd_get_customer_by( 'user_id', $user->ID ); + if ( ! $customer && empty( edd_get_option( 'logged_in_only', '' ) ) ) { + return true; + } + + return new \WP_Error( 'email_used', __( 'Email already used. Login or use a different email to complete your purchase.', 'easy-digital-downloads' ) ); + } + + /** + * Validates an email address for a logged in user. + * + * @since 3.3.7 + * @param string $email The email address to validate. + * @return bool|\WP_Error + */ + private function validate_logged_in_email( $email ) { + if ( ! is_email( $email ) ) { + return new \WP_Error( 'invalid_email', __( 'Please enter a valid email address.', 'easy-digital-downloads' ) ); + } + + $user = wp_get_current_user(); + $user_check = get_user_by( 'email', $email ); + if ( $user_check && (int) $user_check->ID !== (int) $user->ID ) { + return new \WP_Error( 'email_used', __( 'Email already used. Log in or use a different email to complete your purchase.', 'easy-digital-downloads' ) ); + } + + $user_email = $user->user_email; + $emails_to_check = array_unique( + array( + $email, + strtolower( $email ), + $user_email, + strtolower( $user_email ), + ) + ); + + $customer = edd_get_customer_by( 'user_id', get_current_user_id() ); + + // If the current user has a customer record and the email address matches, we're good to go. + if ( ! empty( $customer->email ) && in_array( strtolower( $customer->email ), $emails_to_check, true ) ) { + return true; + } + + $email_args = array( + 'email__in' => $emails_to_check, + ); + if ( $customer ) { + $email_args['customer_id__not_in'] = array( $customer->id ); + } + $matching_emails = edd_get_customer_email_addresses( $email_args ); + if ( empty( $matching_emails ) ) { + return true; + } + + $existing_customer = false; + // Check if any of the matching emails belong to an existing customer. + foreach ( $matching_emails as $matching_email ) { + $email_customer = edd_get_customer( $matching_email->customer_id ); + if ( $email_customer && (int) $email_customer->user_id !== (int) $user->ID ) { + $existing_customer = true; + break; + } + } + + if ( ! $existing_customer ) { + return true; + } + + return new \WP_Error( + 'edd-customer-email-exists', + /* translators: %s: email address */ + sprintf( __( 'The email address %s is already in use.', 'easy-digital-downloads' ), $email ) + ); + } +} diff --git a/src/Checkout/Loader.php b/src/Checkout/Loader.php new file mode 100644 index 00000000000..8c4c05358cd --- /dev/null +++ b/src/Checkout/Loader.php @@ -0,0 +1,56 @@ + 'add_events', + ); + } + + /** + * Add the email events. + * + * @since 3.3.5 + * @return void + */ + public function add_events() { + $checkout_classes = array( + AutoRegister::get_instance(), + new Errors(), + ); + + $events = new EventManager(); + foreach ( $checkout_classes as $checkout_class ) { + $events->add_subscriber( $checkout_class ); + } + } +} diff --git a/src/Checkout/Validator.php b/src/Checkout/Validator.php new file mode 100644 index 00000000000..e6f35e3aa58 --- /dev/null +++ b/src/Checkout/Validator.php @@ -0,0 +1,171 @@ +post_content : ''; + } + + /** + * Checks the WP_Query object. + * + * @since 3.3.0 + */ + private static function check_wp_query() { + global $wp_query; + + self::$is_object_set = isset( $wp_query->queried_object ); + self::$is_object_id_set = isset( $wp_query->queried_object_id ); + self::$is_wp_query_set = ! empty( $wp_query->query ); + } + + /** + * Resets the WP_Query object if needed. + * + * @since 3.3.0 + */ + private static function reset_wp_query() { + global $wp_query; + + if ( ! self::$is_object_set ) { + unset( $wp_query->queried_object ); + } + if ( ! self::$is_object_id_set ) { + unset( $wp_query->queried_object_id ); + } + } + + /** + * Checks if the static value can be returned. + * + * @since 3.3.0 + * @return bool + */ + private static function can_return_static_value() { + if ( ! is_bool( self::$is_checkout ) ) { + return false; + } + + return did_action( 'template_redirect' ) && ! edd_is_doing_unit_tests(); + } +} diff --git a/src/Compat/Base.php b/src/Compat/Base.php new file mode 100644 index 00000000000..eff7699b979 --- /dev/null +++ b/src/Compat/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(); +} diff --git a/src/Compat/Customer.php b/src/Compat/Customer.php new file mode 100644 index 00000000000..b3c628ed06e --- /dev/null +++ b/src/Compat/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/src/Compat/CustomerMeta.php b/src/Compat/CustomerMeta.php new file mode 100644 index 00000000000..9a80343d90c --- /dev/null +++ b/src/Compat/CustomerMeta.php @@ -0,0 +1,117 @@ +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/src/Compat/Discount.php b/src/Compat/Discount.php new file mode 100644 index 00000000000..db62bea6890 --- /dev/null +++ b/src/Compat/Discount.php @@ -0,0 +1,552 @@ +get( 'post_type' ) ) { + return; + } + + // Force filters to run. + $query->set( 'suppress_filters', false ); + + global $wpdb; + + // Setup doing-it-wrong message. + $message = sprintf( + /* translators: 1: posts table, 2: adjustments table, 3: edd_get_discounts(), 4: edd_get_discount(), 5: EDD_Discount, 6: development URL */ + __( '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 + * @deprecated 3.2.0 + * + * @param string $query SQL query. + * @return string $request Rewritten SQL query. + */ + public function wp_count_posts( $query ) { + global $wpdb; + + $expected = md5( strtolower( "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = 'edd_discount' GROUP BY post_status" ) ); + if ( md5( strtolower( $query ) ) === $expected ) { + return "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/src/Compat/Discount_Query.php b/src/Compat/Discount_Query.php new file mode 100644 index 00000000000..00c7685c3c0 --- /dev/null +++ b/src/Compat/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/src/Compat/Log.php b/src/Compat/Log.php new file mode 100644 index 00000000000..9175c45fa79 --- /dev/null +++ b/src/Compat/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/src/Compat/Payment.php b/src/Compat/Payment.php new file mode 100644 index 00000000000..3ad30aed703 --- /dev/null +++ b/src/Compat/Payment.php @@ -0,0 +1,310 @@ +posts} WHERE post_type = 'edd_payment' GROUP BY post_status" ) ); + if ( md5( strtolower( $query ) ) === $expected ) { + return "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 The WP_Query instance (passed by reference). + */ + public function pre_get_posts( $query ) { + + 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 ); + + global $wpdb; + // Setup doing-it-wrong message. + $message = sprintf( + /* translators: 1: wp_posts table, 2: edd_orders table, 3: edd_get_orders(), 4: edd_get_order(), 5: EDD development blog */ + __( '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 + */ + return (array) apply_filters( 'edd_30_post_meta_key_whitelist', $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 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/src/Compat/Tax.php b/src/Compat/Tax.php new file mode 100644 index 00000000000..bbef9dff9a4 --- /dev/null +++ b/src/Compat/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/src/Compat/Template.php b/src/Compat/Template.php new file mode 100644 index 00000000000..1f1d658e82f --- /dev/null +++ b/src/Compat/Template.php @@ -0,0 +1,120 @@ +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 ) { + ?> +
    +

    + +

    ', '' ); ?>

    +

    +
    + is_host_detected() ) { + return; + } + + // Now that we've confirmed the plugin is active, let's register the events. + $this->register_events(); + } + + /** + * Check if the we've found a specific host. + * + * If the host is not found, the compatibility class will not register its events. + * For each host compatibility class, this method should be overridden to check if the host is detected + * using the best method for the host, usually by class_exists, function_exists, or a specific constant. + * + * @since 3.3.4 + * + * @return bool + */ + abstract public function is_host_detected(): bool; + + /** + * Register the events for the host compatibility. + * + * This should typically hold any add_action or add_filter calls for the host compatibility. + * + * @since 3.3.4 + * + * @return void + */ + abstract protected function register_events(); +} diff --git a/src/Compatibility/Hosts/Pantheon.php b/src/Compatibility/Hosts/Pantheon.php new file mode 100644 index 00000000000..eb1795247bf --- /dev/null +++ b/src/Compatibility/Hosts/Pantheon.php @@ -0,0 +1,63 @@ + array( 'load_plugin_compatibility', 9999 ), + 'plugins_loaded' => array( 'load_host_compatibility', 9999 ), + ); + } + + /** + * Load the compatibility classes + * + * @since 3.2.8 + * + * @return void + */ + public static function load_plugin_compatibility() { + $plugin_compatibility_classes = array( + Plugins\Wordfence::class, + ); + + foreach ( $plugin_compatibility_classes as $plugin_compatibility_class ) { + if ( + class_exists( $plugin_compatibility_class ) && + is_subclass_of( $plugin_compatibility_class, 'EDD\Compatibility\Plugins\Plugin' ) + ) { + // Instantiate the class which checks if it is active, registers hooks/filters if it is. + $plugin_class = new $plugin_compatibility_class(); + $plugin_is_active = $plugin_class->is_active(); + + // Store the result of the check. + self::$loaded['plugins'][ $plugin_class->plugin_name ] = $plugin_is_active; + } + } + } + + /** + * Load the host compatibility classes + * + * @since 3.3.4 + * + * @return void + */ + public function load_host_compatibility() { + $host_compatibility_classes = array( + Hosts\Pantheon::class, + Hosts\WPEngine::class, + ); + + foreach ( $host_compatibility_classes as $host_compatibility_class ) { + if ( + class_exists( $host_compatibility_class ) && + is_subclass_of( $host_compatibility_class, 'EDD\Compatibility\Hosts\Host' ) + ) { + $host_class = new $host_compatibility_class(); + $host_is_detected = $host_class->is_host_detected(); + + // Store the result of the check. + self::$loaded['hosts'][ $host_class->host_name ] = $host_is_detected; + } + } + } + + /** + * Get the loaded compatibility classes + * + * @since 3.2.8 + * + * @return array + */ + public static function get_loaded() { + return self::$loaded; + } +} diff --git a/src/Compatibility/Plugins/Plugin.php b/src/Compatibility/Plugins/Plugin.php new file mode 100644 index 00000000000..64741671ca7 --- /dev/null +++ b/src/Compatibility/Plugins/Plugin.php @@ -0,0 +1,62 @@ +is_active() ) { + return; + } + + // Now that we've confirmed the plugin is active, let's register the events. + $this->register_events(); + } + + /** + * Check if the plugin is active + * + * If the plugin is not active, the compatibility class will not register its events. + * For each plugin compatibility class, this method should be overridden to check if the plugin is active + * using the best method for the plugin, usually by class_exists or function_exists. + * + * @since 3.2.8 + * + * @return bool + */ + abstract public function is_active(): bool; + + /** + * Register the events for the plugin compatibility. + * + * This should typically hold any add_action or add_filter calls for the plugin compatibility. + * + * @since 3.2.8 + * + * @return void + */ + abstract protected function register_events(); +} diff --git a/src/Compatibility/Plugins/Wordfence.php b/src/Compatibility/Plugins/Wordfence.php new file mode 100644 index 00000000000..2ebd2ffe56a --- /dev/null +++ b/src/Compatibility/Plugins/Wordfence.php @@ -0,0 +1,62 @@ +pass_handler ), + new Admin\Extensions\Extension_Manager(), + new Customers\Recalculations(), + new Downloads\Services(), + new Orders\DeferredActions(), + new Emails\Loader(), + new Globals\Loader(), + new Integrations\Registry(), + new Checkout\Loader(), + + // Gateways. + new Gateways\Stripe\Webhooks\Listener(), + + // Upgrades. + new Upgrades\Loader(), + + // Compatibility. + new Compatibility\Loader(), + + // Cron Loader. + new Cron\Loader(), + + // API. + new API\WP\Attachments(), + ); + } + + /** + * Gets the admin service providers. + * + * @since 3.1.1 + * @return array + */ + protected function get_admin_providers() { + if ( ! is_admin() ) { + return array(); + } + + $providers = array( + new Admin\Styles(), + 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\Downloads\Metabox(), + new Admin\Promos\Footer\Loader(), + new Admin\Promos\About(), + new Admin\Settings\Pointers(), + new Admin\Menu\Header(), + new Admin\Notifications\Loader(), + ); + + return $providers; + } + + /** + * Gets providers that may be extended/replaced in lite/pro. + * + * @return array + */ + protected function get_replaceable_providers() { + return array( + 'Admin\Extensions\Legacy' => new Admin\Extensions\Legacy(), + 'Admin\Promos\PromoHandler' => new Admin\Promos\PromoHandler(), + 'Admin\Discounts\Generate' => new Admin\Discounts\Generate(), + ); + } + + /** + * Gets the replaceable provider class names. + * + * This allows us to conditionally load the pro version of a provider. + * + * @since 3.2.0 + * @return array + */ + protected function get_replaceable_core_provider_classes() { + return array( + 'Admin\Extensions\Legacy' => 'EDD\Admin\Extensions\Legacy', + 'Admin\Promos\PromoHandler' => 'EDD\Admin\Promos\PromoHandler', + 'Admin\Discounts\Generate' => 'EDD\Admin\Discounts\Generate', + ); + } +} diff --git a/src/Cron/Components/Cart.php b/src/Cron/Components/Cart.php new file mode 100644 index 00000000000..d5adae1bb09 --- /dev/null +++ b/src/Cron/Components/Cart.php @@ -0,0 +1,88 @@ + 'delete_saved_carts', + ); + } + + /** + * Delete saved carts from the database. + * + * @since 3.3.0 + * + * @return void + */ + public function delete_saved_carts() { + global $wpdb; + + $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 ( empty( $carts ) ) { + return; + } + + foreach ( $carts as $cart ) { + $user_id = $cart['user_id']; + $meta_value = $cart['date']; + + if ( strtotime( $meta_value ) > strtotime( '-1 week' ) ) { + continue; + } + + $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', + ) + ); + } + } +} diff --git a/src/Cron/Components/Component.php b/src/Cron/Components/Component.php new file mode 100644 index 00000000000..93c1c5f4ef0 --- /dev/null +++ b/src/Cron/Components/Component.php @@ -0,0 +1,87 @@ +add_subscriber( $this ); + } +} diff --git a/src/Cron/Components/EmailSummaries.php b/src/Cron/Components/EmailSummaries.php new file mode 100644 index 00000000000..0fc18b6f75e --- /dev/null +++ b/src/Cron/Components/EmailSummaries.php @@ -0,0 +1,181 @@ + 'schedule_cron_events', + 'updated_option' => array( 'settings_changed', 10, 3 ), + self::CRON_EVENT_NAME => 'run_cron', + ); + } + + /** + * Get the current status of email summary. + * + * @since 3.1 + * + * @return bool True if email summary is enabled, false if disabled. + */ + public function is_enabled() { + return (bool) ! edd_get_option( 'disable_email_summary', false ); + } + + /** + * Determine when the next cron event + * should be and schedule it. + * + * @since 3.1 + * + * @return void + */ + public function schedule_cron_events() { + // Exit if email summary is disabled or event is already scheduled. + if ( ! $this->is_enabled() || SingleEvent::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() ) ); + SingleEvent::add( + $date->getTimestamp(), + self::CRON_EVENT_NAME + ); + } + + /** + * Clear all cron events related to email summary. + * + * @since 3.1 + * @deprecated 3.3.0 + * + * @return void + */ + public function clear_cron_events() { + _edd_deprecated_function( __METHOD__, '3.3.0', 'EDD\Cron\Components\EmailSummaries::clear()' ); + + self::clear( 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( self::CRON_EVENT_NAME ); + $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(); + + EmailSummariesBlurbs::clear( EmailSummariesBlurbs::CRON_EVENT_NAME ); + delete_option( 'edd_email_summary_blurbs' ); + + // Schedule the next event. + $this->schedule_cron_events(); + } +} diff --git a/src/Cron/Components/EmailSummariesBlurbs.php b/src/Cron/Components/EmailSummariesBlurbs.php new file mode 100644 index 00000000000..1bc901005d5 --- /dev/null +++ b/src/Cron/Components/EmailSummariesBlurbs.php @@ -0,0 +1,140 @@ + 'schedule_cron_events', + self::CRON_EVENT_NAME => 'run_cron', + ); + } + + /** + * Get the current status of email summary. + * + * @since 3.3.3 + * + * @return bool True if email summary is enabled, false if disabled. + */ + public function is_enabled() { + return (bool) ! edd_get_option( 'disable_email_summary', false ); + } + + /** + * Determine when the next cron event + * should be and schedule it. + * + * @since 3.3.3 + * @return void + */ + public function schedule_cron_events() { + // Exit if email summary is disabled or event is already scheduled. + if ( ! $this->is_enabled() || SingleEvent::next_scheduled( self::CRON_EVENT_NAME ) ) { + return; + } + + $timestamp = $this->get_timestamp(); + if ( ! $timestamp ) { + return; + } + + SingleEvent::add( + $timestamp, + self::CRON_EVENT_NAME + ); + } + + /** + * Initialize the cron with all the proper checks. + * + * @since 3.3.3 + * @return void + */ + public function run_cron() { + // This is not cron, abort! + if ( ! wp_doing_cron() ) { + return; + } + + $blurbs = new \EDD_Email_Summary_Blurb(); + $blurbs->fetch_blurbs(); + + // Schedule the next event. + $this->schedule_cron_events(); + } + + /** + * Gets a timestamp to schedule retrieving the blurbs. + * + * @since 3.3.3 + * @return int The timestamp. + */ + private function get_timestamp() { + $next_event = SingleEvent::next_scheduled( EmailSummaries::CRON_EVENT_NAME ); + if ( ! $next_event ) { + return false; + } + + // Schedule the next event between 1 and 96 hours from now. + $timestamp = $next_event - mt_rand( HOUR_IN_SECONDS, 96 * HOUR_IN_SECONDS ); + + // If the timestamp is not in the past, return it. + if ( $timestamp >= time() ) { + return $timestamp; + } + + // Try to schedule it for five minutes out as long as that's still before $next_event. + $timestamp = time() + ( 5 * MINUTE_IN_SECONDS ); + if ( $timestamp < $next_event ) { + return $timestamp; + } + + return time(); + } +} diff --git a/src/Cron/Components/Exports.php b/src/Cron/Components/Exports.php new file mode 100644 index 00000000000..affe82dc4c3 --- /dev/null +++ b/src/Cron/Components/Exports.php @@ -0,0 +1,108 @@ + 'clean_exports', + ); + } + + /** + * Clean up the exports directory. + * + * @since 3.3.0 + * + * @return void + */ + public function clean_exports() { + $exports_dir = edd_get_exports_dir(); + $files = scandir( $exports_dir ); + + if ( ! empty( $files ) ) { + foreach ( $files as $file ) { + if ( '.' === $file[0] ) { + continue; + } + + $full_path = trailingslashit( $exports_dir ) . $file; + + if ( is_dir( $full_path ) || ( 'index.php' === basename( $full_path ) || 'index.html' === basename( $full_path ) ) ) { + continue; + } + + $modified_time = filemtime( $full_path ); + + // If the file hasn't been modified in the last 2 hours, delete it. + if ( time() - $modified_time > HOUR_IN_SECONDS * 2 ) { + unlink( $full_path ); + } + } + } + + // Now ensure that there are no files in the main uploads directory. + $uploads_dir = wp_upload_dir(); + $files = scandir( $uploads_dir['basedir'] ); + + if ( ! empty( $files ) ) { + foreach ( $files as $file ) { + if ( '.' === $file[0] ) { + continue; + } + + $full_path = trailingslashit( $uploads_dir['basedir'] ) . $file; + + if ( is_dir( $full_path ) || ( 'index.php' === basename( $full_path ) || 'index.html' === basename( $full_path ) ) ) { + continue; + } + + // If the file does not have `edd-` in the name, skip it. + if ( false === strpos( $file, 'edd-' ) ) { + continue; + } + + // If the file is not a CSV, skip it. + if ( '.csv' !== substr( $file, -4 ) ) { + continue; + } + + $modified_time = filemtime( $full_path ); + + // If the file hasn't been modified in the last 2 hours, delete it. + if ( time() - $modified_time > HOUR_IN_SECONDS * 2 ) { + unlink( $full_path ); + } + } + } + } +} diff --git a/src/Cron/Components/Notifications.php b/src/Cron/Components/Notifications.php new file mode 100644 index 00000000000..0b24b7246df --- /dev/null +++ b/src/Cron/Components/Notifications.php @@ -0,0 +1,53 @@ + 'get_notifications', + ); + } + + /** + * Get the notifications and send them. + * + * @since 3.3.0 + * + * @return void + */ + public function get_notifications() { + $importer = new NotificationImporter(); + $importer->run(); + } +} diff --git a/src/Cron/Components/Orders.php b/src/Cron/Components/Orders.php new file mode 100644 index 00000000000..837543a9bca --- /dev/null +++ b/src/Cron/Components/Orders.php @@ -0,0 +1,72 @@ + 'mark_abandoned_orders', + ); + } + + /** + * Mark any orders over a week old as abandoned. + * + * @since 3.3.0 + * + * @return void + */ + public function mark_abandoned_orders() { + // Get EDD orders over a week old that are pending. + $before_date = new \DateTime( '-1 week', new \DateTimeZone( 'UTC' ) ); + $orders = edd_get_orders( + array( + 'status' => 'pending', + 'type' => 'sale', + 'number' => 9999999, + 'date_created_query' => array( + 'before' => $before_date->format( 'Y-m-d H:i:s' ), + 'inclusive' => false, + ), + 'fields' => array( 'id', 'status' ), + ) + ); + + if ( $orders ) { + foreach ( $orders as $order ) { + // Just to make sure, only update orders that are pending. + if ( 'pending' === $order->status ) { + edd_update_order_status( $order->id, 'abandoned' ); + } + } + } + } +} diff --git a/src/Cron/Components/Passes.php b/src/Cron/Components/Passes.php new file mode 100644 index 00000000000..ea818f7e179 --- /dev/null +++ b/src/Cron/Components/Passes.php @@ -0,0 +1,77 @@ + 'weekly_license_check', + ); + } + + /** + * Check if license key is valid once per week + * + * @since 3.1.1 + * @return void|bool + */ + 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 false; + } + + // 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, + ); + + $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/Cron/Components/Sessions.php b/src/Cron/Components/Sessions.php new file mode 100644 index 00000000000..8d4f04e5d65 --- /dev/null +++ b/src/Cron/Components/Sessions.php @@ -0,0 +1,71 @@ + 'remove_expired_sessions', + ); + } + + /** + * Deletes all expired sessions from the database. + * This uses Berlin instead of directly querying the database + * to make use of Berlin's caching support. + * + * @since 3.3.0 + * @return void + */ + public function remove_expired_sessions() { + $query = new Session(); + $expired_sessions = $query->query( + array( + 'number' => 500, + 'order' => 'ASC', + 'orderby' => 'session_expiry', + 'session_expiry__compare' => array( + 'relation' => 'AND', + array( + 'value' => time(), + 'compare' => '<', + ), + ), + ) + ); + + if ( empty( $expired_sessions ) ) { + return; + } + + foreach ( $expired_sessions as $session ) { + $query->delete_item( $session->session_id ); + } + } +} diff --git a/src/Cron/Components/Store.php b/src/Cron/Components/Store.php new file mode 100644 index 00000000000..80690eb4ec7 --- /dev/null +++ b/src/Cron/Components/Store.php @@ -0,0 +1,53 @@ + 'send', + ); + } + + /** + * Send the data. + * + * @since 3.3.0 + * + * @return void + */ + public function send() { + EDD()->tracking->send_checkin(); + } +} diff --git a/src/Cron/Components/Stripe.php b/src/Cron/Components/Stripe.php new file mode 100644 index 00000000000..61a7c7fd571 --- /dev/null +++ b/src/Cron/Components/Stripe.php @@ -0,0 +1,63 @@ + 'cleanup_rate_limiting_log', + 'edd_daily_scheduled_events' => 'check_license', + ); + } + + /** + * Clean up the rate limiting log. + * + * @since 3.3.0 + */ + public function cleanup_rate_limiting_log() { + edd_stripe()->rate_limiting->cleanup_log(); + } + + /** + * Check the Stripe license. + * + * @since 3.3.0 + * + * @return void + */ + public function check_license() { + $license_manager = new LicenseManager(); + $license_manager->check_license(); + } +} diff --git a/src/Cron/Events/DailyEvents.php b/src/Cron/Events/DailyEvents.php new file mode 100644 index 00000000000..53c1a3d3e13 --- /dev/null +++ b/src/Cron/Events/DailyEvents.php @@ -0,0 +1,50 @@ +valid = $this->validate(); + } + + /** + * Validate the event. + * + * Validates the event before registering it, to ensure all the required properties are set, and + * to allow for custom first_run and arguments to be defined. + * + * @since 3.3.0 + * + * @throws Exceptions\Attribute_Not_Found If the hook and schedule are not set. + * @throws Exceptions\Invalid_Argument If the event is already scheduled. + * + * @return bool + */ + private function validate() { + try { + if ( empty( $this->hook ) || empty( $this->schedule ) ) { + throw new Exceptions\Attribute_Not_Found( __( 'A hook and schedule are required to schedule an event.', 'easy-digital-downloads' ) ); + } + + if ( empty( $this->first_run ) ) { + $this->first_run = $this->calculate_first_run(); + } + + if ( empty( $this->args ) ) { + $this->args = $this->build_args(); + } + + if ( $this->next_scheduled( $this->hook, $this->args ) ) { + /* translators: %s: hook name that would be run for this WP Cron event. */ + throw new Exceptions\Invalid_Argument( sprintf( __( 'The event %s is already scheduled.', 'easy-digital-downloads' ), $this->hook ) ); + } + } catch ( \Exception $e ) { + return false; + } + + return true; + } + + /** + * Calculate the first run time. + * + * By default the Event class takes a unix timestamp for the first run time. + * If you need to do date calculations to get a specific time for the first run, set the class value as 0 and override this method. + * + * @since 3.3.0 + * + * @return int + */ + private function calculate_first_run() { + $now = time(); + + // If the first run is in the past, set it to now. + if ( $this->first_run < $now ) { + $this->first_run = $now; + } + + return $this->first_run; + } + + /** + * Build the arguments to pass to the hook when the event is run. + * + * By default, the Event class does not pass any arguments to the hook. + * If you need to pass arguments into your cron hook, override this method. + * + * @since 3.3.0 + * + * @return array + */ + private function build_args() { + return array(); + } + + /** + * Schedule an event. + * + * @since 3.3.0 + * + * @return void + */ + public function schedule() { + if ( ! $this->valid ) { + return; + } + + wp_schedule_event( $this->first_run, $this->schedule, $this->hook, $this->args ); + } +} diff --git a/src/Cron/Events/SessionCleanup.php b/src/Cron/Events/SessionCleanup.php new file mode 100644 index 00000000000..a4b3595427b --- /dev/null +++ b/src/Cron/Events/SessionCleanup.php @@ -0,0 +1,47 @@ +getMessage(); + return false; + } + + return true; + } + + /** + * Schedule the event. + * + * @since 3.3.0 + * + * @return void + */ + private static function schedule() { + wp_schedule_single_event( self::$run_time, self::$hook, self::$args ); + } + + /** + * Unschedule the event. + * + * @since 3.3.7 + * + * @return void + */ + private static function unschedule() { + wp_unschedule_event( self::$run_time, self::$hook, self::$args ); + } +} diff --git a/src/Cron/Events/StripeRateLimitingCleanup.php b/src/Cron/Events/StripeRateLimitingCleanup.php new file mode 100644 index 00000000000..34824648119 --- /dev/null +++ b/src/Cron/Events/StripeRateLimitingCleanup.php @@ -0,0 +1,47 @@ + 'load_schedules', + 'init' => 'load_events', + 'plugins_loaded' => array( 'load_components', 999 ), // Run this late, to ensure plugins are loaded. + ); + } + + /** + * Add our custom schedules to the cron schedules. + * + * @since 3.3.0 + * + * @return array + */ + public function load_schedules( $schedules ) { + foreach ( $this->get_registered_schedules() as $schedule ) { + // If this isn't a subclass of Schedule, skip it. + if ( ! is_subclass_of( $schedule, 'EDD\Cron\Schedules\Schedule' ) ) { + continue; + } + + if ( $schedule->valid ) { + $schedules[ $schedule->id ] = array( + 'interval' => $schedule->interval, + 'display' => $schedule->display_name, + ); + } + } + + return $schedules; + } + + /** + * Load any cron events we need to. + * + * Cron Events are the 'do_action' events that are fired by WordPress, on a defined schedule. + * + * @since 3.3.0 + * + * @return void + */ + public function load_events() { + foreach ( $this->get_registered_events() as $event ) { + // If this isn't a subclass of Event, skip it. + if ( ! is_subclass_of( $event, 'EDD\Cron\Events\Event' ) ) { + continue; + } + + $event->schedule(); + } + } + + /** + * Load any components registered that have cron events. + * + * @since 3.3.0 + * + * @return void + */ + public function load_components() { + // We'll collect the final list of components to register here. + $final_components_list = array(); + + foreach ( $this->get_registered_components() as $component_class ) { + // If this isn't a subclass of Component, skip it. + if ( ! is_subclass_of( $component_class, 'EDD\Cron\Components\Component' ) ) { + continue; + } + + // If the array key exists, and the class isn't set to overload, skip it. + if ( array_key_exists( $component_class::get_id(), $final_components_list ) && ! $component_class::should_overload() ) { + continue; + } + + // Either the array key doesn't exist already, or we are intentionally overloading it, so add it to the list. + $final_components_list[ $component_class::get_id() ] = $component_class; + } + + /** + * Load the components. + * + * Since these extend the Component class, they should all be using the SubscriberInterface to hook into the events. + */ + foreach ( $final_components_list as $component_class ) { + $component = new $component_class(); + + // Since this loads to late, we need to trigger the event management now. + $component->subscribe(); + } + } + + /** + * Get the registered schedules. + * + * @since 3.3.0 + * + * @return array + */ + private function get_registered_schedules() { + $registered_schedules = array(); + + /** + * Filter the registered cron schedules. + * + * @since 3.3.0 + * + * @param array $registered_schedules The currently registered cron schedules. + * + * Example: + * add_filter( 'edd_cron_schedules', function( $registered_schedules ) { + * $registered_schedules[] = new MyCustomSchedule(); + * return $registered_schedules; + * } ); + * + * @return array + */ + $registered_schedules = apply_filters( 'edd_cron_schedules', $registered_schedules ); + + // Since we have a filter here, if something goes wrong return an empty array. + if ( ! is_array( $registered_schedules ) ) { + return array(); + } + + return $registered_schedules; + + } + + /** + * Get the registered events. + * + * @since 3.3.0 + * + * @return array + */ + private function get_registered_events() { + $registered_events = array( + new Events\DailyEvents(), + new Events\WeeklyEvents(), + new Events\StripeRateLimitingCleanup(), + ); + + /** + * Filter the registered cron events. + * + * @since 3.3.0 + * + * @param array $registered_events The currently registered cron events. + * + * Example: + * add_filter( 'edd_cron_events', function( $registered_events ) { + * $registered_events[] = new MyCustomEvent(); + * return $registered_events; + * } ); + * + * @return array + */ + $registered_events = apply_filters( 'edd_cron_events', $registered_events ); + + // Since we have a filter here, if something goes wrong return an empty array. + if ( ! is_array( $registered_events ) ) { + return array(); + } + + return $registered_events; + } + + /** + * Get the registered components. + * + * @since 3.3.0 + * + * @return array + */ + private function get_registered_components() { + // Register our components. + $components_to_register = array( + Components\Cart::class, + Components\EmailSummaries::class, + Components\Exports::class, + Components\Notifications::class, + Components\Orders::class, + Components\Passes::class, + Components\Store::class, + Components\Stripe::class, + Components\EmailSummariesBlurbs::class, + ); + + /** + * Filter the components to register. + * + * @since 3.3.0 + * + * @param array $components_to_register The currently registered cron components. + * + * Example: + * add_filter( 'edd_cron_components', function( $components_to_register ) { + * $components_to_register[] = MyNameSpace\MyClass::class; + * return $components_to_register; + * } ); + * + * @return array + */ + $components_to_register = apply_filters( 'edd_cron_components', $components_to_register ); + + // Since we have a filter here, if something goes wrong return an empty array. + if ( ! is_array( $components_to_register ) ) { + return array(); + } + + return $components_to_register; + } +} diff --git a/src/Cron/Schedules/Schedule.php b/src/Cron/Schedules/Schedule.php new file mode 100644 index 00000000000..e7410aa5021 --- /dev/null +++ b/src/Cron/Schedules/Schedule.php @@ -0,0 +1,100 @@ +display_name = $this->get_display_name(); + $this->valid = $this->validate(); + } + + /** + * Validates the schedule. + * + * @since 3.3.0 + * + * @throws Exception If the schedule is not valid. + * + * @return bool + */ + private function validate() { + try { + if ( empty( $this->id ) || empty( $this->interval ) || empty( $this->display_name ) ) { + throw new Exception( __( 'An ID, interval, and display name must be provided.', 'easy-digital-downloads' ) ); + } + + // The minimum interval is 5 minutes, for now. + if ( $this->interval < 300 ) { + throw new Exception( __( 'The interval must be at least 5 minutes.', 'easy-digital-downloads' ) ); + } + } catch ( \Exception $e ) { + return false; + } + + return true; + } + + /** + * Get the display name for the schedule. + * + * This must be an abstract class to be implemented by the extending class. Since display names need to be translated, + * they cannot be set as a class property, and therefore must be implemented via a method that returns the translatable string. + * + * @since 3.3.0 + * + * @return string + */ + abstract protected function get_display_name(): string; +} diff --git a/src/Cron/Schedules/SessionCleanup.php b/src/Cron/Schedules/SessionCleanup.php new file mode 100644 index 00000000000..7ee71d0a46f --- /dev/null +++ b/src/Cron/Schedules/SessionCleanup.php @@ -0,0 +1,42 @@ +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..38ebfd89f74 --- /dev/null +++ b/src/Currency/Money_Formatter.php @@ -0,0 +1,270 @@ +original_amount = $amount; + $this->amount = $amount; + $this->typed_amount = $amount; + $this->currency = $currency; + $this->data_amount = $amount; + } + + /** + * 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; + } + + /** + * Formats the amount for data storage. + * Does not apply the currency code. + * This does set the amount to the correct number of decimal places. + * + * @since 3.3.6 + * @param bool $decimals If we should include decimal places or not when formatting. + * @return Money_Formatter + */ + public function format_for_data( $decimals = true ) { + $amount = $this->unformat(); + + if ( empty( $amount ) ) { + $amount = 0; + } + + $decimals = $this->get_decimals( $decimals, $amount ); + + $this->data_amount = number_format( round( floatval( (float) $amount ), $decimals ), $decimals, '.', '' ); + + 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..5238e3b1f3c --- /dev/null +++ b/src/Customers/Recalculations.php @@ -0,0 +1,143 @@ + 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 ( is_object( $previous_order ) ) { + // If the order item data being updated doesn't affect sales/earnings, recalculations do not need to be run. + if ( ! $this->should_recalculate_from_previous( $data, $previous_order ) ) { + return; + } + + // Recalculate the previous product values if the product ID has changed. + if ( isset( $data['customer_id'] ) && $previous_order->customer_id != $data['customer_id'] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + $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 The 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 The customer ID. + * @return void + */ + private function schedule_recalculation( $customer_id ) { + if ( empty( $customer_id ) ) { + return; + } + $is_scheduled = SingleEvent::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 ); + SingleEvent::add( + time() + ( 5 * MINUTE_IN_SECONDS ), + 'edd_recalculate_customer_deferred', + array( $customer_id ) + ); + } + + /** + * Determines if the customer stats should be recalculated based on the previous order data. + * + * @param array $data The array of order data. + * @param stdClass $previous_order The previous order object. + * @return bool + */ + private function should_recalculate_from_previous( $data, $previous_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 false; + } + + foreach ( $columns_affecting_stats as $key ) { + if ( isset( $data[ $key ] ) && $previous_order->$key != $data[ $key ] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + return true; + } + } + + return false; + } +} diff --git a/src/Database/NotificationsDB.php b/src/Database/NotificationsDB.php new file mode 100644 index 00000000000..ee171968121 --- /dev/null +++ b/src/Database/NotificationsDB.php @@ -0,0 +1,281 @@ +is_block_editor ) { + return; + } + $version = edd_admin_get_script_version(); + $css_suffix = is_rtl() ? '-rtl.min.css' : '.min.css'; + + wp_register_script( 'alpinejs', EDD_PLUGIN_URL . 'assets/js/alpine.min.js', array(), '3.4.2', true ); + wp_enqueue_script( 'edd-admin-notifications', EDD_PLUGIN_URL . 'assets/js/edd-admin-notifications.js', array( 'alpinejs' ), $version, true ); + wp_enqueue_style( 'edd-admin-notifications', EDD_PLUGIN_URL . 'assets/css/edd-admin-notifications' . $css_suffix, array(), $version ); + } + + /** + * Add `defer` to the AlpineJS script tag. + * + * @since 3.2.4 + */ + public function defer_alpine( $url ) { + $alpine = wp_make_link_relative( EDD_PLUGIN_URL . 'assets/js/alpine.min.js' ); + if ( false !== strpos( $url, $alpine ) ) { + $url = str_replace( ' src', ' defer src', $url ); + } + + return $url; + } + + /** + * 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/Email.php b/src/Database/Queries/Email.php new file mode 100644 index 00000000000..e6fce6273cc --- /dev/null +++ b/src/Database/Queries/Email.php @@ -0,0 +1,96 @@ +get_column_field( array( 'primary' => true ), 'name', 'session_id' ); + } +} diff --git a/src/Database/Rows/Email.php b/src/Database/Rows/Email.php new file mode 100644 index 00000000000..c5b73cfab54 --- /dev/null +++ b/src/Database/Rows/Email.php @@ -0,0 +1,32 @@ + 'id', + 'type' => 'bigint', + 'length' => 20, + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true, + ), + + // email_id. + array( + 'name' => 'email_id', + 'type' => 'varchar', + 'length' => 32, + 'allow_null' => false, + // 'uuid' => true, + ), + + // context. + array( + 'name' => 'context', + 'type' => 'varchar', + 'length' => 32, + 'allow_null' => false, + 'default' => 'order', + ), + + // sender. + array( + 'name' => 'sender', + 'type' => 'varchar', + 'length' => 32, + 'allow_null' => false, + 'default' => 'edd', + ), + + // recipient. + array( + 'name' => 'recipient', + 'type' => 'varchar', + 'length' => 32, + 'allow_null' => false, + 'default' => 'customer', + ), + + // subject. + array( + 'name' => 'subject', + 'type' => 'text', + 'allow_null' => false, + 'searchable' => true, + ), + + // heading. + array( + 'name' => 'heading', + 'type' => 'text', + 'allow_null' => true, + 'default' => null, + 'searchable' => true, + ), + + // content. + array( + 'name' => 'content', + 'type' => 'longtext', + 'allow_null' => false, + 'searchable' => true, + ), + + // status. + array( + 'name' => 'status', + 'type' => 'tinyint', + 'length' => 1, + 'unsigned' => true, + 'allow_null' => false, + 'default' => 0, + ), + + // date_created. + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'allow_null' => false, + 'default' => '', + 'created' => true, + ), + + // date_modified. + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'allow_null' => false, + 'default' => '', + 'modified' => true, + ), + ); +} diff --git a/src/Database/Schemas/LogsEmails.php b/src/Database/Schemas/LogsEmails.php new file mode 100644 index 00000000000..71598a07d0b --- /dev/null +++ b/src/Database/Schemas/LogsEmails.php @@ -0,0 +1,112 @@ + '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, + 'sortable' => true, + 'allow_null' => false, + ), + + // object type. + array( + 'name' => 'object_type', + 'type' => 'varchar', + 'length' => 20, + 'allow_null' => false, + 'default' => 'order', + ), + + // email. + array( + 'name' => 'email', + 'type' => 'varchar', + 'length' => 100, + 'default' => '', + 'searchable' => true, + 'sortable' => true, + ), + + // email_id. + array( + 'name' => 'email_id', + 'type' => 'varchar', + 'length' => 32, + 'unsigned' => true, + 'sortable' => true, + 'allow_null' => false, + ), + + // subject. + array( + 'name' => 'subject', + 'type' => 'varchar', + 'length' => 200, + 'default' => '', + 'sortable' => true, + 'searchable' => true, + 'allow_null' => false, + ), + + // date_created. + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', + 'created' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // date_modified. + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', + 'modified' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // uuid. + array( + 'uuid' => true, + ), + ); +} diff --git a/src/Database/Schemas/Notifications.php b/src/Database/Schemas/Notifications.php new file mode 100644 index 00000000000..9646ba4c96e --- /dev/null +++ b/src/Database/Schemas/Notifications.php @@ -0,0 +1,151 @@ + '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/Schemas/Sessions.php b/src/Database/Schemas/Sessions.php new file mode 100644 index 00000000000..33b1b60143a --- /dev/null +++ b/src/Database/Schemas/Sessions.php @@ -0,0 +1,72 @@ + 'session_id', + 'type' => 'bigint', + 'length' => 20, + 'unsigned' => true, + 'auto_increment' => true, + 'primary_key' => true, + ), + + // session_key. + array( + 'name' => 'session_key', + 'type' => 'varchar', + 'length' => 64, + 'allow_null' => false, + 'unique' => true, + 'cache_key' => true, + ), + + // session_value. + array( + 'name' => 'session_value', + 'type' => 'longtext', + 'allow_null' => false, + ), + + // session_expiry. + array( + 'name' => 'session_expiry', + 'type' => 'bigint', + 'length' => 20, + 'unsigned' => true, + 'allow_null' => false, + 'compare' => true, + ), + ); +} diff --git a/src/Database/Tables/EmailMeta.php b/src/Database/Tables/EmailMeta.php new file mode 100644 index 00000000000..b93478f6d7b --- /dev/null +++ b/src/Database/Tables/EmailMeta.php @@ -0,0 +1,70 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_email_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY email_id (edd_email_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/src/Database/Tables/Emails.php b/src/Database/Tables/Emails.php new file mode 100644 index 00000000000..be8c1922edd --- /dev/null +++ b/src/Database/Tables/Emails.php @@ -0,0 +1,76 @@ +schema = " + id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + email_id varchar(32) NOT NULL, + context varchar(32) NOT NULL DEFAULT 'order', + sender varchar(32) NOT NULL DEFAULT 'edd', + recipient varchar(32) NOT NULL DEFAULT 'customer', + subject text NOT NULL, + heading text DEFAULT NULL, + content longtext NOT NULL, + status tinyint(1) UNSIGNED NOT NULL DEFAULT 0, + date_created datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY email_id (email_id)"; + } +} diff --git a/src/Database/Tables/LogsEmailMeta.php b/src/Database/Tables/LogsEmailMeta.php new file mode 100644 index 00000000000..eaa2dc5899b --- /dev/null +++ b/src/Database/Tables/LogsEmailMeta.php @@ -0,0 +1,63 @@ +schema = " + meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_logs_email_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_email_id (edd_logs_email_id), + KEY meta_key (meta_key({$max_index_length})) + "; + } +} diff --git a/src/Database/Tables/LogsEmails.php b/src/Database/Tables/LogsEmails.php new file mode 100644 index 00000000000..93466f40f03 --- /dev/null +++ b/src/Database/Tables/LogsEmails.php @@ -0,0 +1,77 @@ +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 'customer', + email varchar(100) NOT NULL default '', + email_id varchar(32) NOT NULL, + subject varchar(200) 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 object_id_type (object_id,object_type(20)), + KEY email_id (email_id), + KEY date_created (date_created) + "; + } +} 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/Database/Tables/Sessions.php b/src/Database/Tables/Sessions.php new file mode 100644 index 00000000000..3533096f027 --- /dev/null +++ b/src/Database/Tables/Sessions.php @@ -0,0 +1,71 @@ +schema = ' + session_id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + session_key varchar(64) NOT NULL, + session_value longtext NOT NULL, + session_expiry bigint(20) UNSIGNED NOT NULL, + PRIMARY KEY (session_id), + KEY session_key (session_key), + KEY session_expiry (session_expiry) + '; + } +} diff --git a/src/Deprecated/EmptyClass.php b/src/Deprecated/EmptyClass.php new file mode 100644 index 00000000000..c928574f4e2 --- /dev/null +++ b/src/Deprecated/EmptyClass.php @@ -0,0 +1,36 @@ +' . $name, '3.2.10' ); + + return isset( $this->$name ) ? $this->$name : null; + } +} diff --git a/src/Discounts/ItemAmount.php b/src/Discounts/ItemAmount.php new file mode 100644 index 00000000000..502212e9a66 --- /dev/null +++ b/src/Discounts/ItemAmount.php @@ -0,0 +1,341 @@ +items = array_map( array( $this, 'normalize_item' ), $items ); + $this->item = $this->normalize_item( $item ); + $this->discounts = $this->get_discounts( $discounts ); + $this->item_unit_price = $this->get_item_unit_price( $item_unit_price ); + } + + /** + * Get the discount amount. + * + * @since 3.2.0 + * + * @return float + */ + public function get_discount_amount() { + global $edd_flat_discount_total; + + // Return early if the item is not valid. + if ( empty( $this->item ) || empty( $this->item['id'] ) || empty( $this->item['quantity'] ) ) { + return 0; + } + + // If there are no discounts, return 0. + if ( empty( $this->discounts ) ) { + return 0; + } + + $item_amount = ( $this->item_unit_price * $this->item['quantity'] ); + $discount_amount = 0; + + foreach ( $this->discounts as $discount ) { + + // Make sure the discount is not excluded from this item. + $excluded_products = array_map( 'intval', $discount->get_excluded_products() ); + if ( in_array( $this->item['id'], $excluded_products, true ) ) { + continue; + } + + // Get the product requirements. + $product_requirements = $discount->get_product_reqs(); + if ( ! empty( $product_requirements ) ) { + $processed = false; + + // This is a product(s) specific discount. + foreach ( $product_requirements as $requirement ) { + + $parsed_requirement = edd_parse_product_dropdown_value( $requirement ); + if ( $parsed_requirement['download_id'] === $this->item['id'] ) { + + $price_id = isset( $this->item['options']['price_id'] ) && is_numeric( $this->item['options']['price_id'] ) ? absint( $this->item['options']['price_id'] ) : null; + + // If there is no price ID on the requirement, or the requirement price ID matches the item's price ID, apply the discount. + if ( is_null( $parsed_requirement['price_id'] ) || $parsed_requirement['price_id'] === $price_id ) { + $discount_amount += ( $item_amount - $discount->get_discounted_amount( $item_amount ) ); + $processed = true; + // Break the requirements loop since the discount is applied for the current cart item. + break; + } + } + } + + // Discount calculation is done for this discount, so continue to the next discount. + if ( $processed || 'global' !== $discount->get_scope() ) { + continue; + } + } + + // Check the category requirements. + if ( ! $discount->is_valid_for_categories( false, array( $this->item['id'] ) ) ) { + continue; + } + + // This is a global cart discount. + + // Get the discount amount for a percentage discount. + if ( 'flat' !== $discount->get_type() ) { + $discount_amount += ( $item_amount - $discount->get_discounted_amount( $item_amount ) ); + continue; + } + + // Get the discount amount for a flat discount. + $items_amount = $this->get_items_amount( $excluded_products ); + $subtotal_percent = ! empty( $items_amount ) ? ( $item_amount / $items_amount ) : 0; + $item_discount = $discount->get_amount() * $subtotal_percent; + + // Make adjustments on the last item. + if ( $this->is_last_item() ) { + $other_items_discount = $this->get_other_items_discount( $items_amount, $discount, $excluded_products ); + $adjustment = $discount->get_amount() - ( $item_discount + $other_items_discount ); + $item_discount += $adjustment; + } + + $discount_amount += $item_discount; + + // Make sure the discount amount doesn't exceed the item amount. + if ( $discount_amount > $item_amount ) { + $discount_amount = $item_amount; + } + + // Add the discount amount to the global flat discount total. + $edd_flat_discount_total += $discount_amount; + } + + return edd_format_amount( $discount_amount, true, '', 'data' ); + } + + /** + * Normalize the item. + * + * @since 3.2.0 + * @param array $item The item. + * @return array + */ + private function normalize_item( $item ) { + + if ( empty( $item['id'] ) ) { + return array(); + } + + 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']; + } + } + + // Get the hash from the item number. + if ( isset( $item['item_number']['hash'] ) ) { + $item['hash'] = $item['item_number']['hash']; + } + + // Generate a hash if one is not set. + if ( ! isset( $item['hash'] ) ) { + $item['hash'] = md5( serialize( $item ) . time() . wp_rand( 0, 1000 ) ); + } + + // Cast the product ID to an integer. + $item['id'] = absint( $item['id'] ); + + return $item; + } + + /** + * Normalize the discounts. + * + * @since 3.2.0 + * + * @param array $discounts The discounts. + * @return array + */ + private function get_discounts( $discounts ) { + // 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 + ); + + return array_filter( $discounts ); + } + + /** + * Get the item unit price. + * + * @since 3.2.0 + * + * @param float $item_unit_price The item unit price. + * @param array $item The item. + * @return float + */ + private function get_item_unit_price( $item_unit_price, $item = false ) { + if ( false !== $item_unit_price ) { + return $item_unit_price; + } + + if ( false === $item ) { + $item = $this->item; + } + + if ( empty( $item['id'] ) ) { + return 0; + } + + // 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; + } + + return edd_get_price_option_amount( $item['id'], $item['options']['price_id'] ); + } + + return edd_get_download_price( $item['id'] ); + } + + /** + * Get the items amount. 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. + * + * @since 3.2.0 + * + * @param array $excluded_products The excluded products. + * @return float + */ + private function get_items_amount( $excluded_products ) { + $items_amount = 0; + + foreach ( $this->items as $key => $i ) { + + $i = $this->normalize_item( $i ); + if ( in_array( $i['id'], $excluded_products, true ) ) { + continue; + } + + $i_amount = $this->get_item_unit_price( false, $i ); + + $this->items[ $key ]['amount'] = ( $i_amount * $i['quantity'] ); + $items_amount += $this->items[ $key ]['amount']; + } + + return $items_amount; + } + + /** + * Check if the item is the last item. + * + * @since 3.2.0 + * @return bool + */ + private function is_last_item() { + $last_item = end( $this->items ); + if ( ! empty( $this->item['hash'] ) && ! empty( $last_item['hash'] ) ) { + if ( hash_equals( $this->item['hash'], $last_item['hash'] ) ) { + return true; + } + } + + return false; + } + + /** + * Get the discount for all items except the last. + * + * @since 3.2.0 + * + * @param float $items_amount The items amount. + * @param \EDD_Discount $discount The discount. + * @param array $excluded_products The excluded products. + * @return float + */ + private function get_other_items_discount( $items_amount, $discount, $excluded_products ) { + return array_reduce( + $this->items, + function ( $carry, $_item ) use ( $items_amount, $discount, $excluded_products ) { + + $percent = 0; + // Calculate percent only if current item is not same as loop item and not in excluded products. + if ( ! hash_equals( $this->item['hash'], $_item['hash'] ) && ! empty( $items_amount ) && ! in_array( $_item['id'], $excluded_products, true ) ) { + $percent = $_item['amount'] / $items_amount; + } + $value = edd_format_amount( $discount->get_amount() * $percent, true, '', 'data' ); + + return $carry + $value; + }, + 0 + ); + } +} diff --git a/src/Downloads/Process.php b/src/Downloads/Process.php new file mode 100644 index 00000000000..66453012a34 --- /dev/null +++ b/src/Downloads/Process.php @@ -0,0 +1,54 @@ +should_recalculate_from_previous( $data, $previous_order_item, array( 'status', 'quantity', 'total', 'subtotal', 'discount', 'tax', 'rate', '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'] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + 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 ); + } + + /** + * 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|stdClass $previous_order_adjustment The previous order adjustment object. + * @return void + */ + public function recalculate_order_adjustment( $order_adjustment_id, $data = array(), $previous_order_adjustment = false ) { + if ( get_option( '_edd_v30_doing_order_migration', false ) ) { + return; + } + + if ( is_object( $previous_order_adjustment ) ) { + if ( ! $this->should_recalculate_from_previous( $data, $previous_order_adjustment, array( 'total', 'subtotal', 'object_id', '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 ); + } + } + + /** + * Determines if a recalculation should be scheduled based on the previous order item data and the new data. + * + * @since 3.1 + * @param array $data The new order item data. + * @param bool|mixed $previous_item The previous order item object. + * @param array $columns_affecting_stats The columns that affect sales/earnings. + * @return bool + */ + private function should_recalculate_from_previous( $data, $previous_item, $columns_affecting_stats ) { + + // 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 false; + } + + foreach ( $columns_affecting_stats as $key ) { + if ( isset( $data[ $key ] ) && $previous_item->$key != $data[ $key ] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + return true; + } + } + + return false; + } +} diff --git a/src/Downloads/Search.php b/src/Downloads/Search.php new file mode 100644 index 00000000000..418e853cafb --- /dev/null +++ b/src/Downloads/Search.php @@ -0,0 +1,269 @@ +search() ); + + edd_die(); + } + + /** + * Search for downloads. + * + * @since 3.3.6 + * @return array + */ + public function search() { + $search = $this->get_results(); + + // Update the transient. + set_transient( 'edd_download_search', $search, 30 ); + + return $search['results']; + } + + /** + * Get the search results. + * + * @since 3.3.6 + * @return array + */ + private function get_results() { + + // We store the last search in a transient for 30 seconds. This _might_ + // result in a race condition if 2 users are looking at the exact same time, + // but we'll worry about that later if that situation ever happens. + $args = get_transient( 'edd_download_search' ); + + // Parse args. + $search = wp_parse_args( + (array) $args, + array( + 'text' => '', + 'results' => array(), + ) + ); + + // Get the search string. + $new_search = $this->get_search(); + + // Bail early if the search text has not changed. + if ( $search['text'] === $new_search ) { + return $search; + } + + // Set the local static search variable and clear the results. + $search = array( + 'text' => $new_search, + 'results' => array(), + ); + + // Default query arguments. + $args = array( + 'orderby' => 'title', + 'order' => 'ASC', + 'post_type' => 'download', + 'posts_per_page' => 50, + 'post_status' => $this->get_status(), + 'post__not_in' => $this->get_exclusions(), + 's' => $new_search, + 'suppress_filters' => false, + ); + + $items = $this->get_items( $args ); + + if ( empty( $items ) ) { + return $search; + } + + // 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; + + $items = wp_list_pluck( $items, 'post_title', 'ID' ); + + // Loop through all items... + foreach ( $items as $post_id => $title ) { + // Skip bundles if we're excluding them. + if ( true === $no_bundles && 'bundle' === edd_get_download_type( $post_id ) ) { + continue; + } + $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 ), + ); + } + } + } + } + + return $search; + } + + /** + * Gets the items. + * + * @since 3.2.8 + * @param array $args The array of arguments for WP_Query. + * @return array + */ + private function get_items( $args ) { + add_filter( + 'post_search_columns', + function () { + return array( 'post_title' ); + } + ); + + return get_posts( $args ); + } + + /** + * Gets the search string. + * + * @since 3.2.7 + * @return string + */ + private function get_search() { + return isset( $_GET['s'] ) + ? sanitize_text_field( urldecode( $_GET['s'] ) ) + : ''; + } + + /** + * Gets the excluded downloads. + * + * @since 3.2.8 + * @return array + */ + private function get_exclusions() { + $excludes = ! empty( $_GET['current_id'] ) + ? array_map( 'absint', (array) $_GET['current_id'] ) + : array(); + + if ( ! empty( $_GET['exclusions'] ) ) { + $excludes = array_merge( $excludes, array_map( 'absint', explode( ',', $_GET['exclusions'] ) ) ); + } + + return array_unique( array_filter( $excludes ) ); + } + + /** + * Get the download statuses to query. + * + * @since 3.3.6 + * @return array + */ + private function get_status() { + if ( ! current_user_can( 'edit_products' ) ) { + return apply_filters( 'edd_product_dropdown_status_nopriv', array( 'publish' ) ); + } + + return apply_filters( 'edd_product_dropdown_status', array( 'publish', 'draft', 'private', 'future' ) ); + } + + /** + * 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. + * @deprecated 3.3.6 + * @param string $where The WHERE clause of the query. + * @return string + */ + public function filter_where( $where ) { + return $where; + } + + /** + * Parses the search terms to allow for a "fuzzy" search. + * + * @since 3.1.0.5 + * @deprecated 3.3.6 + * @param string $search The search string. + * @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/Downloads/Service.php b/src/Downloads/Service.php new file mode 100644 index 00000000000..48cf61e70b6 --- /dev/null +++ b/src/Downloads/Service.php @@ -0,0 +1,54 @@ +ID, $price_id ) ) { + return false; + } + + if ( 'service' === $this->type ) { + return true; + } + + // If the product type is explicitly set to something other than the default, return false. + if ( ! in_array( $this->type, array( '', 'default' ), true ) ) { + return false; + } + + $terms = array_filter( edd_get_option( 'edd_das_service_categories', array() ) ); + if ( empty( $terms ) ) { + return false; + } + + return has_term( $terms, 'download_category', $this->ID ); + } +} diff --git a/src/Downloads/Services.php b/src/Downloads/Services.php new file mode 100644 index 00000000000..2a9809f1462 --- /dev/null +++ b/src/Downloads/Services.php @@ -0,0 +1,188 @@ + array( 'modify_receipt', 10, 4 ), + 'edd_receipt_no_files_found_text' => array( 'no_files_found_text', 10, 2 ), + 'edd_get_download_type' => array( 'get_download_type', 20, 2 ), + 'edd_settings_marketing' => 'settings', + 'edd_get_option_edd_das_service_categories' => 'maybe_update_option', + ); + } + + /** + * Modifies the receipt to hide the download files for services. + * + * @since 3.2.0 + * @param bool $show Whether to show the download files. + * @param int $item_id The item ID. + * @param array $receipt_args The receipt arguments. + * @param object $order_item The order item. + * @return bool + */ + public function modify_receipt( $show, $item_id, $receipt_args, $order_item ) { + return $this->is_download_service( $item_id, $order_item->price_id ) ? false : $show; + } + + /** + * Modifies the "No Files Found" text to be empty for services. + * + * @since 3.2.0 + * @param string $text The text to display. + * @param int $download_id The download ID. + * @return string + */ + public function no_files_found_text( $text, $download_id ) { + return $this->is_download_service( $download_id ) ? '' : $text; + } + + /** + * Returns the download type for services. + * + * @since 3.2.0 + * @param string $type The download type. + * @param int $download_id The download ID. + * @return string + */ + public function get_download_type( $type, $download_id ) { + if ( 'service' === $type ) { + return $type; + } + + // If the download doesn't yet have a type, but does have AA settings, it's probably an AA download. + if ( ( empty( $type ) || 'default' === $type ) && get_post_meta( $download_id, '_edd_das_enabled', true ) ) { + update_post_meta( $download_id, '_edd_product_type', 'service' ); + delete_post_meta( $download_id, '_edd_das_enabled' ); + + return 'service'; + } + + return $type; + } + + /** + * Adds the settings field for selecting service categories. + * + * @since 3.2.0 + * @param array $settings The settings array. + * @return array + */ + public function settings( $settings ) { + $settings['main']['edd_das_service_categories'] = array( + 'id' => 'edd_das_service_categories', + 'name' => __( 'Downloads as Services', 'easy-digital-downloads' ), + 'desc' => __( 'Select the categories that contain services, or products with no downloadable files.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => $this->get_terms_options(), + 'multiple' => true, + 'chosen' => true, + 'placeholder' => __( 'Select categories', 'easy-digital-downloads' ), + 'std' => array(), + ); + + return $settings; + } + + /** + * Updates the service categories option to be an array of term IDs. + * + * @since 3.2.0 + * @param array $value The option value. + * @return array + */ + public function maybe_update_option( $value ) { + if ( ! is_array( $value ) ) { + return $value; + } + + // If the value is already an array of term IDs, return it. + if ( array_key_exists( 0, $value ) ) { + return $value; + } + + // The original extension used the term name as the value and the term name as the value. + $terms = array_filter( array_keys( $value ) ); + if ( ! $terms ) { + return $value; + } + + $terms = array_map( 'absint', $terms ); + $terms = array_unique( $terms ); + + return $terms; + } + + /** + * Determines if the download is a service. + * + * @since 3.2.0 + * @param int $download_id The download ID. + * @param int|null $price_id The price ID. + * @return bool + */ + private function is_download_service( $download_id, $price_id = null ) { + $download = new Service( $download_id ); + + return $download->is_service( $price_id ); + } + + /** + * Returns an array of terms for the settings field. + * + * @since 3.2.0 + * @return array + */ + private function get_terms_options() { + $options = array(); + if ( ! function_exists( 'edd_is_admin_page' ) || ! edd_is_admin_page( 'settings' ) ) { + return $options; + } + + $args = apply_filters( + 'edd_das_get_terms', + array( + 'taxonomy' => 'download_category', + 'hide_empty' => false, + 'hierarchical' => false, + ) + ); + $terms = get_terms( $args ); + if ( ! $terms || is_wp_error( $terms ) ) { + return $options; + } + + foreach ( $terms as $term ) { + $options[ $term->term_id ] = $term->name; + } + + return $options; + } +} diff --git a/src/Emails/Base.php b/src/Emails/Base.php new file mode 100644 index 00000000000..3c08f4d6d51 --- /dev/null +++ b/src/Emails/Base.php @@ -0,0 +1,422 @@ +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 ) { + // If we have a setter, use it. + if ( method_exists( $this, "set_{$key}" ) ) { + $this->{"set_{$key}"}( $value ); + } else { + $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. + 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(); + + /** + * Added in 3.2.0, we need to replace {heading} with the heading string. + * + * This is compatible with old and new uses of this class. + */ + if ( ! empty( $this->heading ) ) { + // Replace the {heading} with the heading string. + $body = str_replace( '{heading}', $this->get_heading(), $body ); + + // Remove the {begin_heading} and {end_heading} tags. + $body = str_replace( '{begin_heading}', '', $body ); + $body = str_replace( '{end_heading}', '', $body ); + } else { + // There is no heading so remove everything between {begin_heading} and {end_heading}, inclusive. + $body = preg_replace( '/{begin_heading}.*{end_heading}/s', '', $body ); + } + + // Replace the email body now. + $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 = wp_strip_all_tags( $this->parse_tags( $subject ), true ); + $message = $this->parse_tags( $message ); + + $message = $this->build_email( $message ); + $headers = $this->get_headers(); + $attachments = apply_filters( 'edd_email_attachments', $attachments, $this ); + + if ( empty( $to ) || empty( $subject ) || empty( $message ) || empty( $headers ) ) { + return false; + } + + $sent = wp_mail( $to, $subject, $message, $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( + /* translators: 1: To address, 2: Subject */ + __( "Email from Easy Digital Downloads failed to send. \nTo: %1\$s\nSubject: %2\$s\n\n", 'easy-digital-downloads' ), + $to, + $subject + ); + + edd_debug_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; + } + + /** + * Set the from_address if someone uses the `from_email` property. + * + * @since 3.2.4 + */ + private function set_from_email( $from_email ) { + _edd_deprecated_function( __FUNCTION__, '3.2.0', 'EDD\Emails\Base->__set(\'from_address\', \'\'' ); + + // Since this is now `from_address` we need to set the old `from_email` and `from_address`. + $this->from_email = $from_email; + $this->from_address = $from_email; + } +} diff --git a/src/Emails/Email.php b/src/Emails/Email.php new file mode 100644 index 00000000000..6143bebe75c --- /dev/null +++ b/src/Emails/Email.php @@ -0,0 +1,179 @@ +{$key} = $value; + } + + // Return null if not exists. + return null; + } + + /** + * Gets the email template. + * + * @since 3.3.0 + * @return EDD\Emails\Templates\EmailTemplate|false + */ + public function get_template() { + return edd_get_email_registry()->get_email_by_id( $this->email_id, $this ); + } + + /** + * Gets the email status. + * + * @since 3.3.0 + * @return bool + */ + public function is_enabled() { + $template = $this->get_template(); + if ( $template ) { + return $template->status; + } + + return $this->status; + } + + /** + * Gets the email recipient. + * + * @since 3.3.0 + * @param null|mixed $email_object The email type object. + * @return array|false + */ + public function get_admin_recipient_emails( $email_object = null ) { + if ( 'admin' !== $this->recipient ) { + return false; + } + $meta = edd_get_email_meta( $this->id, 'recipients', true ); + // If the email was created before the meta was correctly saved, default to admin email in settings. + if ( empty( $meta ) && gmdate( 'H:i', strtotime( $this->date_created ) ) === gmdate( 'H:i', strtotime( $this->date_modified ) ) ) { + $meta = 'admin'; + edd_update_email_meta( $this->id, 'recipients', $meta ); + } + + // If there isn't a custom recipient, default to the admin email. + if ( empty( $meta ) ) { + return get_bloginfo( 'admin_email' ); + } + + // If the meta is 'admin', get the admin emails from the EDD settings. + if ( 'admin' === $meta ) { + return edd_get_admin_notice_emails( $email_object ); + } + + return array_map( 'trim', explode( "\n", $meta ) ); + } +} diff --git a/src/Emails/Handler.php b/src/Emails/Handler.php new file mode 100644 index 00000000000..b8ce61afa5f --- /dev/null +++ b/src/Emails/Handler.php @@ -0,0 +1,47 @@ + array( 'register', 3 ), + ); + } + + /** + * Register the EDD core emails. + * + * @since 3.3.0 + * @return void + */ + public function register() { + if ( ! edd_has_upgrade_completed( 'edd_emails_registered' ) ) { + $emails = new \EDD\Database\Tables\Emails(); + $emails->maybe_upgrade(); + $meta = new \EDD\Database\Tables\EmailMeta(); + $meta->maybe_upgrade(); + } + foreach ( Registry::get_emails() as $key => $email_class ) { + Registry::register( $key, $email_class ); + } + } +} diff --git a/src/Emails/Legacy.php b/src/Emails/Legacy.php new file mode 100644 index 00000000000..02ebfe071ec --- /dev/null +++ b/src/Emails/Legacy.php @@ -0,0 +1,107 @@ + 'remove_legacy_data', + 'edd_get_option' => array( 'get_option', 10, 2 ), + ); + } + + /** + * Removes legacy data associated with the specified email ID. + * + * @param int $email_id The ID of the email to remove legacy data for. + * @return void + */ + public function remove_legacy_data( $email_id ) { + $email = edd_get_email_by( 'id', $email_id ); + if ( ! $email ) { + return; + } + $template = $email->get_template(); + if ( ! $template ) { + return; + } + + $template->remove_legacy_data(); + } + + /** + * Retrieves the value of a specific option from the Legacy class. + * + * @since 3.3.0 + * @param string $value The value to retrieve. + * @param string $key The key of the option. + * @return mixed The value of the option. + */ + public function get_option( $value, $key ) { + if ( ! did_action( 'edd_setup_components' ) ) { + return $value; + } + static $legacy = null; + + if ( is_null( $legacy ) || edd_is_doing_unit_tests() ) { + global $wpdb; + $table_exists = $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}edd_emailmeta'" ); + if ( $table_exists ) { + // Get all email meta with a meta_key of 'legacy'. + $legacy = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}edd_emailmeta WHERE meta_key = %s", 'legacy' ) ); + } + } + if ( empty( $legacy ) ) { + $legacy = false; + + return $value; + } + + // Create an array of legacy option names and their corresponding email IDs. + $option_names = wp_list_pluck( $legacy, 'edd_email_id', 'meta_value' ); + if ( ! array_key_exists( $key, $option_names ) ) { + return $value; + } + + // Get the email object for the option. + $email = edd_get_email_registry()->get_email_by_id( $option_names[ $key ] ); + if ( ! $email ) { + return $value; + } + + // Deprecate the use of edd_get_option() for email options. + _edd_deprecated_function( 'edd_get_option( \'' . $key . '\' )', '3.3.0' ); + + // Get the options for the email. + $options = $email->options; + if ( empty( $options ) || ! in_array( $key, $options, true ) ) { + return $value; + } + + // Get the option key for the email. + $option_key = array_search( $key, $options, true ); + if ( ! $option_key ) { + return $value; + } + + return $email->{$option_key}; + } +} diff --git a/src/Emails/Loader.php b/src/Emails/Loader.php new file mode 100644 index 00000000000..ee7b922e01d --- /dev/null +++ b/src/Emails/Loader.php @@ -0,0 +1,62 @@ + 'add_events', + ); + } + + /** + * Add the email events. + * + * @since 3.3.0 + * @return void + */ + public function add_events() { + $email_classes = array( + new Handler(), + new Triggers(), + new Legacy(), + ); + + if ( is_admin() ) { + $email_classes[] = new \EDD\Admin\Emails\Manager(); + $email_classes[] = new \EDD\Admin\Emails\Messages(); + } + + $events = new EventManager(); + foreach ( $email_classes as $email_class ) { + $events->add_subscriber( $email_class ); + } + } +} diff --git a/src/Emails/LogEmail.php b/src/Emails/LogEmail.php new file mode 100644 index 00000000000..8d349e1466f --- /dev/null +++ b/src/Emails/LogEmail.php @@ -0,0 +1,81 @@ + Types\OrderReceipt::class, + 'admin_order_notice' => Types\AdminOrderNotice::class, + 'order_refund' => Types\OrderRefund::class, + 'new_user' => Types\NewUser::class, + 'new_user_admin' => Types\NewUserAdmin::class, + 'user_verification' => Types\UserVerification::class, + 'admin_order_refund' => Types\AdminOrderRefund::class, + 'stripe_early_fraud_warning' => Types\StripeEarlyFraudWarning::class, + ); + + return apply_filters( 'edd_email_registered_types', $types ); + } + + /** + * Registry constructor. + * + * @since 3.2.0 + * + * @param string $id The email to register. This must be a unique ID. + * @param string $class The fully qualified class to use for this email. + * + * @throws Exception + */ + public static function register( $id = '', $class = '' ) { + try { + // An ID and a class are required. + if ( empty( $id ) || empty( $class ) ) { + throw new Exception( __( 'An email ID and class must be provided.', 'easy-digital-downloads' ) ); + } + + // Verify that this ID is not already registered. + if ( self::is_registered( $id ) ) { + throw new Exception( __( 'The email ID provided is already registered.', 'easy-digital-downloads' ) ); + } + + // Verify that the class exists, and that it extends the EDD\Emails\Types\Email class. + if ( ! class_exists( $class ) || ! is_subclass_of( $class, 'EDD\Emails\Types\Email' ) ) { + throw new Exception( __( 'The email class must exist and extend the EDD\Emails\Types\Email class.', 'easy-digital-downloads' ) ); + } + + // Add the email to the registry. + self::$registered_emails[ $id ] = $class; + } catch ( \Exception $e ) { + wp_die( $e->getMessage() ); + } + } + + /** + * Determine if an ID is regsitered. + * + * @since 3.2.0 + * @param string $id The email ID to check. + * @return bool + */ + public static function is_registered( $id = '' ) { + return isset( self::$registered_emails[ $id ] ); + } + + /** + * Returns the class for a given email ID. + * + * @since 3.2.0 + * @param string $id The email ID to get the class for. + */ + public static function get( $id = '', $arguments = array() ) { + try { + if ( ! self::is_registered( $id ) ) { + throw new Exception( __( 'The email ID provided is not registered.', 'easy-digital-downloads' ) ); + } + + $class = self::$registered_emails[ $id ]; + $reflection = new \ReflectionClass( $class ); + $constructor = $reflection->getConstructor(); + $required_arguments = $constructor->getNumberOfRequiredParameters(); + + if ( count( $arguments ) < $required_arguments ) { + throw new Exception( + sprintf( + /* translators: 1: The number of arguments provided. 2: The number of arguments required. 3: The class name. */ + __( 'The number of arguments provided (%1$d) does not match the number of arguments required (%2$d) for %3$s.', 'easy-digital-downloads' ), + count( $arguments ), + $required_arguments, + $class + ) + ); + } + + return new $class( ...$arguments ); + } catch ( \Exception $e ) { + wp_die( $e->getMessage() ); + } + } +} diff --git a/src/Emails/Tags/Handler.php b/src/Emails/Tags/Handler.php new file mode 100644 index 00000000000..9bc943f9d2a --- /dev/null +++ b/src/Emails/Tags/Handler.php @@ -0,0 +1,355 @@ + $tag, + 'label' => ! empty( $label ) ? $label : ucwords( str_replace( '_', ' ', $tag ) ), + 'description' => $description, + 'func' => $func, + 'contexts' => $contexts, + 'recipients' => $recipients, + ); + + $this->tags[ $this->get_unique_tag_key( $tag_data ) ] = $tag_data; + } + + /** + * Remove an email tag + * + * @since 1.9 + * + * @param string $tag Email tag to remove hook from. + */ + public function remove( $tag ) { + if ( ! array_key_exists( $tag, $this->tags ) ) { + $tag_name = $this->get_tag_by_name( $tag ); + if ( $tag_name ) { + $tag = $this->get_unique_tag_key( $tag_name ); + } + } + + unset( $this->tags[ $tag ] ); + } + + /** + * Check if $tag is a registered email tag + * + * @since 1.9 + * + * @param string $tag Email tag key that will be searched. + * @param string $context The context to get tags for. + * @param string $recipient The recipient to get tags for. + * @return bool + */ + public function email_tag_exists( $tag, $context = '', $recipient = '' ) { + $tags = $this->get( $context, $recipient ); + if ( array_key_exists( $tag, $tags ) ) { + return true; + } + + $tag_by_name = $this->get_tag_by_name( $tag ); + + return ! empty( $tag_by_name ) && array_key_exists( $this->get_unique_tag_key( $tag_by_name ), $tags ); + } + + /** + * Get all email tags + * + * @since 3.3.0 + * + * @param string $context The context to get tags for. + * @param string $recipient The recipient to get tags for. + * @return array + */ + public function get( $context = '', $recipient = '' ) { + $tags = (array) $this->tags; + if ( empty( $tags ) ) { + return $tags; + } + if ( empty( $context ) && empty( $recipient ) ) { + return $tags; + } + foreach ( $tags as $data ) { + if ( + ! empty( $context ) && ! empty( $data['contexts'] ) && ! in_array( $context, $data['contexts'], true ) || + ! empty( $recipient ) && ! empty( $data['recipients'] ) && ! in_array( $recipient, $data['recipients'], true ) + ) { + unset( $tags[ $this->get_unique_tag_key( $data ) ] ); + } + } + + return $tags; + } + + /** + * Search content for email tags and filter email tags through their hooks + * + * @param string $content Content to search for email tags. + * @param int $object_id The object ID. Originally this was an order ID, but it can be any object ID. + * @param object $email_object The email object. This could be an order, license, user, etc. + * @param string $context_or_email The context or email object (\EDD\Emails\Types\Email). + * + * @since 1.9 + * @since 3.3.0 Added $email_object and $context_or_email parameters. + * + * @return string Content with email tags filtered out. + */ + public function do_tags( $content, $object_id, $email_object = null, $context_or_email = '' ) { + + if ( $context_or_email instanceof \EDD\Emails\Types\Email ) { + $this->email = $context_or_email; + } + $context = $this->get_context( $context_or_email ); + + // Check if there is at least one tag added. + if ( empty( $this->get( $context ) ) ) { + return $content; + } + + $this->object_id = $object_id; + $this->object = $email_object; + $this->context = $context; + + $new_content = $this->handle_content( $content ); + + $this->object_id = null; + $this->object = null; + $this->email = null; + $this->context = ''; + + return $new_content; + } + + /** + * Handles the content of the email. + * + * @since 3.3.0 + * @param string $content The content of the email. + * @return string + */ + private function handle_content( $content ) { + $content = preg_replace_callback( '/{([A-Za-z0-9\-\_]+)}/s', array( $this, 'do_tag' ), $content ); + + /** + * Apply filters to the email content. + * + * This function applies the 'edd_email_content_tags' filter to the provided content, along with additional parameters. + * + * @param string $content The email content to be filtered. + * @param int $object_id The ID of the object associated with the email. + * @param object $object The object associated with the email. + * @param string $email The email being sent. + * @param string $context The context in which the email is being sent. + * @return string The filtered email content. + */ + return apply_filters( 'edd_email_content_tags', $content, $this->object_id, $this->object, $this->email, $this->context ); + } + + /** + * Do a specific tag, this function should not be used directly. Please use edd_do_email_tags instead. + * + * @since 1.9 + * + * @param array $m Array of matches from preg_replace_callback. + * @return string + */ + public function do_tag( $m ) { + + // Get tag by name. + $tag = $this->get_tag_by_name( $m[1], $this->context ); + if ( ! $tag ) { + return $m[0]; + } + + $parameter = ! empty( $this->email ) ? $this->email : $this->context; + + return $this->can_do_tag( $tag ) ? + call_user_func( $tag['func'], $this->object_id, $this->object, $parameter ) : + $m[0]; + } + + /** + * Retrieves the context for the given email or context. + * + * @since 3.3.0 + * @param string|array $email_or_context The email or context for which to retrieve the context. + * @return array The context for the given email or context. + */ + public function get_context( $email_or_context ) { + if ( $email_or_context instanceof \EDD\Emails\Types\Email ) { + return $email_or_context->get_context(); + } + + return ! empty( $email_or_context ) ? $email_or_context : 'order'; + } + + /** + * Check if a tag can be processed. + * + * @since 3.3.0 + * + * @param array $tag_data Tag to check. + * @return bool + */ + private function can_do_tag( $tag_data ) { + if ( ! is_callable( $tag_data['func'] ) ) { + return false; + } + + if ( ! empty( $tag_data['contexts'] ) && ! in_array( $this->context, $tag_data['contexts'], true ) ) { + return false; + } + + return true; + } + + /** + * Get a tag by name. + * + * @since 3.3.0 + * + * @param string $name Name of tag to get. + * @return array|bool + */ + private function get_tag_by_name( $name, $context = '' ) { + $tags = $this->get(); + foreach ( $tags as $tag ) { + if ( $tag['tag'] !== $name ) { + continue; + } + $context_matches = true; + if ( ! empty( $tag['contexts'] ) && ! empty( $context ) ) { + $context_matches = in_array( $context, $tag['contexts'], true ); + } + if ( $context_matches ) { + return $tag; + } + } + + return false; + } + + /** + * Get a unique key for a tag. + * + * @since 3.3.0 + * + * @param array $tag Tag to get unique key for. + * @return string + */ + private function get_unique_tag_key( $tag ) { + if ( empty( $tag ) ) { + return false; + } + + return ! empty( $tag['contexts'] ) ? $tag['tag'] . '_' . reset( $tag['contexts'] ) : $tag['tag']; + } + + /** + * Returns a list of all email tags. + * This function is deprecated, please use get() instead. + * This has been retained for compatibility with classes which extend this class. + * + * @since 1.9 + * @deprecated 3.3.0 Use get() instead. + * @return array + */ + public function get_tags() { + _edd_deprecated_function( __METHOD__, '3.3.0', 'get()' ); + + return $this->get(); + } +} diff --git a/src/Emails/Tags/Registry.php b/src/Emails/Tags/Registry.php new file mode 100644 index 00000000000..ba6717dd9fc --- /dev/null +++ b/src/Emails/Tags/Registry.php @@ -0,0 +1,265 @@ +render = new Render(); + } + + /** + * Registers the email tags. + * + * @since 3.3.0 + * @return void + */ + public function register() { + $email_tags = $this->get_tags(); + + // Add email tags. + foreach ( $email_tags as $email_tag ) { + $label = isset( $email_tag['label'] ) ? $email_tag['label'] : ''; + $contexts = isset( $email_tag['contexts'] ) ? $email_tag['contexts'] : null; + $recipients = isset( $email_tag['recipients'] ) ? $email_tag['recipients'] : null; + edd_add_email_tag( $email_tag['tag'], $email_tag['description'], $email_tag['function'], $label, $contexts, $recipients ); + } + } + + /** + * Retrieves the email tags. + * + * @since 3.3.0 + * @return array + */ + private function get_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', + 'contexts' => array( 'order' ), + ), + 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', + 'contexts' => array( 'order' ), + ), + array( + 'tag' => 'name', + 'label' => __( 'First Name', 'easy-digital-downloads' ), + 'description' => __( "The buyer's (or user's) first name.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_first_name', + 'contexts' => array( 'user', 'order', 'refund' ), + ), + array( + 'tag' => 'fullname', + 'label' => __( 'Full Name', 'easy-digital-downloads' ), + 'description' => __( "The buyer's (or user's) full name: first and last.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_fullname', + 'contexts' => array( 'user', 'order', 'refund' ), + ), + array( + 'tag' => 'username', + 'label' => __( 'Username', 'easy-digital-downloads' ), + 'description' => __( "The buyer's (or user's) user name on the site, if they registered an account.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_username', + 'contexts' => array( 'user', 'order', 'refund' ), + ), + array( + 'tag' => 'user_email', + 'label' => __( 'Email', 'easy-digital-downloads' ), + 'description' => __( "The buyer's (or user's) email address.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_user_email', + 'contexts' => array( 'user', 'order', 'refund' ), + ), + 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', + 'contexts' => array( 'order' ), + ), + array( + 'tag' => 'date', + 'label' => __( 'Purchase Date', 'easy-digital-downloads' ), + 'description' => __( 'The date of the purchase.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_date', + 'contexts' => array( 'order', 'refund' ), + ), + array( + 'tag' => 'subtotal', + 'label' => __( 'Subtotal', 'easy-digital-downloads' ), + 'description' => __( 'The price of the purchase before taxes.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_subtotal', + 'contexts' => array( 'order', 'refund' ), + ), + array( + 'tag' => 'tax', + 'label' => __( 'Tax', 'easy-digital-downloads' ), + 'description' => __( 'The taxed amount of the purchase', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_tax', + 'contexts' => array( 'order', 'refund' ), + ), + array( + 'tag' => 'fees_total', + 'label' => __( 'Fees Total', 'easy-digital-downloads' ), + 'description' => __( 'The total fees on the order, formatted with currency.', 'easy-digital-downloads' ), + 'function' => array( $this->render, 'fees_total' ), + 'contexts' => array( 'order', 'refund' ), + ), + array( + 'tag' => 'fees_list', + 'label' => __( 'Fees List', 'easy-digital-downloads' ), + 'description' => __( 'A list of all fees on the order, with amounts.', 'easy-digital-downloads' ), + 'function' => array( $this->render, 'fees_list' ), + 'contexts' => array( 'order', 'refund' ), + ), + array( + 'tag' => 'price', + 'label' => __( 'Price', 'easy-digital-downloads' ), + 'description' => __( 'The total price of the purchase', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_price', + 'contexts' => array( 'order', 'refund' ), + ), + 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', + 'contexts' => array( 'order', 'refund' ), + ), + 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', + 'contexts' => array( 'order', 'refund' ), + ), + 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', + 'contexts' => array( 'order', 'refund' ), + ), + array( + 'tag' => 'sitename', + 'label' => __( 'Site Name', 'easy-digital-downloads' ), + 'description' => __( 'Your site name.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_sitename', + 'contexts' => array(), + ), + 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', + 'contexts' => array( 'order', 'refund' ), + ), + 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', + 'contexts' => array( 'order', 'refund' ), + ), + 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', + 'contexts' => array( 'order', 'refund' ), + ), + 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', + 'contexts' => array( 'order', 'refund', 'user' ), + ), + array( + 'tag' => 'login_link', + 'label' => __( 'Login Link', 'easy-digital-downloads' ), + 'description' => __( 'The link to log into the site.', 'easy-digital-downloads' ), + 'function' => array( $this->render, 'login_link' ), + 'contexts' => array(), + ), + array( + 'tag' => 'refund_link', + 'label' => __( 'Refund Link', 'easy-digital-downloads' ), + 'description' => __( 'The link to refund record in the EDD admin.', 'easy-digital-downloads' ), + 'function' => array( $this->render, 'refund_link' ), + 'contexts' => array( 'refund' ), + 'recipients' => array( 'admin' ), + ), + array( + 'tag' => 'order_details_link', + 'label' => __( 'Order Details Link', 'easy-digital-downloads' ), + 'description' => __( 'The link to the order details page in the EDD admin.', 'easy-digital-downloads' ), + 'function' => array( $this->render, 'order_details_link' ), + 'contexts' => array( 'order' ), + 'recipients' => array( 'admin' ), + ), + array( + 'tag' => 'transaction_id', + 'label' => __( 'Transaction ID', 'easy-digital-downloads' ), + 'description' => __( 'The merchant transaction ID for this order. This is for admin emails only.', 'easy-digital-downloads' ), + 'function' => array( $this->render, 'transaction_id' ), + 'contexts' => array( 'order' ), + 'recipients' => array( 'admin' ), + ), + array( + 'tag' => 'password_link', + 'label' => __( 'Password Reset Link', 'easy-digital-downloads' ), + 'description' => __( "The link to set the user's password. In an order receipt, this will only be included for the user's first purchase.", 'easy-digital-downloads' ), + 'function' => array( $this->render, 'password_link' ), + 'contexts' => array( 'order', 'user' ), + ), + array( + 'tag' => 'refund_amount', + 'label' => __( 'Refund Amount', 'easy-digital-downloads' ), + 'description' => __( 'The amount that was refunded to the customer.', 'easy-digital-downloads' ), + 'function' => array( $this->render, 'refund_amount' ), + 'contexts' => array( 'refund' ), + ), + array( + 'tag' => 'refund_id', + 'label' => __( 'Refund ID', 'easy-digital-downloads' ), + 'description' => __( 'The unique identifier for this refund.', 'easy-digital-downloads' ), + 'function' => array( $this->render, 'refund_id' ), + 'contexts' => array( 'refund' ), + ), + ); + + // Apply edd_email_tags filter. + return apply_filters( 'edd_email_tags', $email_tags ); + } +} diff --git a/src/Emails/Tags/Render.php b/src/Emails/Tags/Render.php new file mode 100644 index 00000000000..c7a564450c1 --- /dev/null +++ b/src/Emails/Tags/Render.php @@ -0,0 +1,344 @@ +emails->get_content_type() ) { + return wp_login_url(); + } + + return sprintf( + '%2$s', + wp_login_url(), + __( 'Login', 'easy-digital-downloads' ) + ); + } + + /** + * Email tag callback for {password_link}. + * Returns the link for new users; otherwise returns an empty string. + * + * @since 3.3.0 + * @param int $object_id The object ID. + * @param mixed $email_object The email object. + * @param string $context The context. + * @return string + */ + public function password_link( $object_id, $email_object = null, $context = 'order' ) { + $context = EDD()->email_tags->get_context( $context ); + if ( 'order' === $context && ! $email_object instanceof Order ) { + $email_object = edd_get_order( $object_id ); + } + $user = false; + if ( 'order' === $context ) { + if ( ! $email_object instanceof Order ) { + return ''; + } + $user = $this->get_user_data( $email_object->user_id ); + if ( ! $user ) { + return ''; + } + if ( ! $this->is_first_purchase( $email_object->user_id, $email_object->id ) ) { + return ''; + } + } elseif ( 'user' === $context ) { + if ( $email_object instanceof \WP_User ) { + $user = $email_object; + } else { + $user = $this->get_user_data( $object_id ); + } + } + if ( ! $user ) { + return ''; + } + $password_reset_link = edd_get_password_reset_link( $user ); + if ( ! $password_reset_link ) { + return ''; + } + + return sprintf( + '%2$s', + $password_reset_link, + __( 'Set your password', 'easy-digital-downloads' ) + ); + } + + /** + * Renders the refund link email tag. + * + * @since 3.3.0 + * @param int $refund_id Refund ID. + * @param Order $refund Refund object. + * @param string|\EDD\Emails\Types\Email $context_or_email Context or email object. + * @return string + */ + public function refund_link( $refund_id, $refund = null, $context_or_email = '' ) { + if ( $context_or_email instanceof \EDD\Emails\Types\Email ) { + if ( 'admin' !== $context_or_email->recipient_type ) { + return ''; + } + $context = $context_or_email->get_context(); + } else { + $context = $context_or_email; + } + if ( 'refund' !== $context || empty( $refund ) || 'refund' !== $refund->type ) { + return '{refund_link}'; + } + + return edd_get_admin_url( + array( + 'page' => 'edd-payment-history', + 'view' => 'view-refund-details', + 'id' => absint( $refund_id ), + ) + ); + } + + /** + * Renders the order details link email tag. + * + * @since 3.3.0 + * @param int $order_id Order ID. + * @param Order $order Order object. + * @param string $context The context. + * + * @return string + */ + public function order_details_link( $order_id, $order = null, $context = '' ) { + if ( $context instanceof \EDD\Emails\Types\Email ) { + if ( 'admin' !== $context->recipient_type ) { + return ''; + } + $context = $context->get_context(); + } + if ( 'order' !== $context ) { + return '{order_details_link}'; + } + + // If the order object is not a valid order, get it. + if ( ! $order ) { + $order = edd_get_order( $order_id ); + } + + if ( ! $order ) { + return '{order_details_link}'; + } + + return edd_get_admin_url( + array( + 'page' => 'edd-payment-history', + 'view' => 'view-order-details', + 'id' => absint( $order_id ), + ) + ); + } + + /** + * Renders the transaction ID email tag. + * + * @since 3.3.0 + * + * @param int $order_id Order ID. + * @param object $order Order object. + * @param string $context Context. + * @return string + */ + public function transaction_id( $order_id, $order = null, $context = 'order' ) { + if ( $context instanceof \EDD\Emails\Types\Email ) { + if ( 'admin' !== $context->recipient_type ) { + return ''; + } + $context = $context->get_context(); + } + if ( 'order' !== $context ) { + return ''; + } + + if ( empty( $order ) || ! $order instanceof Order ) { + $order = edd_get_order( $order_id ); + } + + // phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores + return apply_filters( 'edd_payment_details_transaction_id-' . $order->gateway, $order->get_transaction_id(), $order->id ); + } + + /** + * Renders the fees total email tag. + * + * @since 3.3.0 + * + * @param int $order_id Order ID. + * @param object $order Order object. + * @return string + */ + public function fees_total( $order_id, $order = null ) { + if ( ! $order instanceof Order ) { + $order = edd_get_order( $order_id ); + } + + $total = array_reduce( + $order ? $order->get_fees() : array(), + function ( $carry, $fee ) { + return $carry + $fee->get_amount(); + }, + 0 + ); + + return edd_currency_filter( edd_format_amount( $total ), $order->currency ); + } + + /** + * Renders the fees list email tag. + * + * @since 3.3.0 + * + * @param int $order_id Order ID. + * @param object $order Order object. + * @return string + */ + public function fees_list( $order_id, $order = null ) { + if ( ! $order instanceof Order ) { + $order = edd_get_order( $order_id ); + } + + $fees = $order ? $order->get_fees() : array(); + + if ( empty( $fees ) ) { + return ''; + } + + return sprintf( + '

      %s
    ', + array_reduce( + $fees, + function ( $carry, $fee ) use ( $order ) { + return $carry . sprintf( + '
  • %s: %s
  • ', + $fee->description ?? __( 'Fee', 'easy-digital-downloads' ), + edd_currency_filter( edd_format_amount( $fee->get_amount() ), $order->currency ) + ); + }, + '' + ) + ); + } + + /** + * Retrieves the total refund amount for a given refund. + * + * @since 3.3.0 + * @param int $order_id The ID of the order. + * @param object|null $refund_order The refund order object. Default is null. + * @param \EDD\Emails\Types\Email|string $context The context of the refund. Default is null. + * @return float The refund amount. + */ + public function refund_amount( $order_id, $refund_order = null, $context = null ) { + if ( $context instanceof \EDD\Emails\Types\Email ) { + $context = $context->get_context(); + } + if ( 'refund' !== $context ) { + return ''; + } + + if ( ! $refund_order instanceof Order ) { + $refund_order = edd_get_order( $order_id ); + } + + // If the order is not a valid refund order, return the tag to allow for the second round of parsing. + if ( ! $refund_order instanceof Order || 'refund' !== $refund_order->type ) { + return '{refund_amount}'; + } + + return edd_currency_filter( edd_format_amount( $refund_order->total * -1 ), $refund_order->currency ); + } + + /** + * Retrieves the refund ID for a given order. + * + * @since 3.3.0 + * @param int $order_id The ID of the order. + * @param object|null $refund The refund order object. Default is null. + * @param \EDD\Emails\Types\Email|string $context The context of the refund. Default is null. + * @return string The refund ID if found, null otherwise. + */ + public function refund_id( $order_id, $refund = null, $context = null ) { + if ( $context instanceof \EDD\Emails\Types\Email ) { + $context = $context->get_context(); + } + if ( 'refund' !== $context ) { + return ''; + } + if ( is_null( $refund ) ) { + $refund = edd_get_order( $order_id ); + } + + if ( $refund instanceof Order && 'refund' === $refund->type ) { + return $refund->order_number; + } + + return '{refund_id}'; + } + + /** + * Check if it the first purchase for a given user. + * + * @since 3.3.0 + * + * @param int $user_id The user ID. + * @param int $order_id The order ID. + * @return bool + */ + private function is_first_purchase( $user_id, $order_id ) { + return empty( + edd_get_orders( + array( + 'type' => 'sale', + 'number' => 1, + 'status__in' => edd_get_complete_order_statuses(), + 'user_id' => $user_id, + 'id__not_in' => array( $order_id ), + ) + ) + ); + } + + /** + * Fetch user data. + * + * @since 3.3.0 + * @param int $user_id The user ID. + * @return WP_User|false WP_User object on success, false on failure. + */ + private function get_user_data( $user_id = 0 ) { + if ( $user_id ) { + return get_userdata( $user_id ); + } + + return false; + } +} diff --git a/src/Emails/Templates/AdminOrderNotice.php b/src/Emails/Templates/AdminOrderNotice.php new file mode 100644 index 00000000000..406683e9b91 --- /dev/null +++ b/src/Emails/Templates/AdminOrderNotice.php @@ -0,0 +1,161 @@ + '', + ); + + /** + * Name of the template. + * + * @since 3.3.0 + * @return string + */ + public function get_name() { + return __( 'Admin Sale Notification', 'easy-digital-downloads' ); + } + + /** + * Description of the email. + * + * @since 3.3.0 + * @return string + */ + public function get_description() { + return __( + 'Text to email as a notification for every completed purchase. Personalize with HTML and {tag} markers.', + 'easy-digital-downloads' + ); + } + + /** + * Define the default email properties. + * + * @since 3.3.0 + * @return array + */ + public function defaults(): array { + return array( + /* translators: %s: The email tag that will be replaced with the payment ID. */ + 'subject' => sprintf( __( 'New download purchase - Order #%s', 'easy-digital-downloads' ), '{payment_id}' ), + 'heading' => __( 'New Sale!', 'easy-digital-downloads' ), + 'content' => $this->get_default_content(), + 'status' => 1, + 'recipients' => 'admin', + ); + } + + /** + * The email properties that can be edited. + * + * @return array + */ + protected function get_editable_properties(): array { + return array( + 'content', + 'subject', + 'heading', + 'status', + 'recipient', + ); + } + + /** + * Gets the default email content. + * + * @since 3.3.0 + * @return string + */ + private function get_default_content() { + $content = __( 'Hello', 'easy-digital-downloads' ); + $content .= "\n\n"; + + /* translators: %s: The plural label for the Download post type. */ + $content .= sprintf( __( 'A %s purchase has been made', 'easy-digital-downloads' ), edd_get_label_plural() ); + $content .= ".\n\n"; + + /* translators: %s: The plural label for the Download post type. */ + $content .= sprintf( __( '%s sold:', 'easy-digital-downloads' ), edd_get_label_plural() ) . "\n\n"; + + $content .= '{download_list}' . "\n\n"; + + /* translators: %s: The email tag that will be replaced by the customer's full name */ + $content .= sprintf( __( 'Purchased by: %s', 'easy-digital-downloads' ), '{fullname}' ) . "\n"; + + /* translators: %s: The email tag that will be replaced by the order total. */ + $content .= sprintf( _x( 'Amount: %s', 'Context: This is a tag (placholder) for email content that will be replaced when sending.', 'easy-digital-downloads' ), '{price}' ) . "\n"; + + /* translators: %s: The email tag that will be replaced by the payment method. */ + $content .= sprintf( __( 'Payment Method: %s', 'easy-digital-downloads' ), '{payment_method}' ) . "\n\n"; + + $content .= __( 'Thank you', 'easy-digital-downloads' ); + + return $content; + } + + /* Legacy */ + /** + * Gets the option names for this email. + * + * @since 3.3.0 + * @return array + */ + protected function get_options(): array { + return array( + 'content' => 'sale_notification', + 'subject' => 'sale_notification_subject', + 'heading' => 'sale_notification_heading', + 'disabled' => 'disable_admin_notices', + ); + } +} diff --git a/src/Emails/Templates/AdminOrderRefund.php b/src/Emails/Templates/AdminOrderRefund.php new file mode 100644 index 00000000000..d48f2a693d0 --- /dev/null +++ b/src/Emails/Templates/AdminOrderRefund.php @@ -0,0 +1,142 @@ + '', + ); + + /** + * Name of the template. + * + * @since 3.3.0 + * @return string + */ + public function get_name() { + return __( 'Admin Refund Notification', 'easy-digital-downloads' ); + } + + /** + * Description of the email. + * + * @since 3.3.0 + * @return string + */ + public function get_description() { + return __( 'Text to email to admin(s) after issuing a refund.', 'easy-digital-downloads' ); + } + + /** + * Define the default email properties. + * + * @since 3.3.0 + * @return array + */ + public function defaults(): array { + return array( + 'subject' => __( 'An order has been refunded', 'easy-digital-downloads' ), + 'content' => $this->get_default_content(), + 'status' => 0, + ); + } + + /** + * Gets the email preview data. + * + * @since 3.3.0 + * @return array + */ + protected function get_preview_data() { + $refund_id = Previews\Data::get_refund_id(); + $refund = edd_get_order( $refund_id ); + + return $refund ? + array( + $refund, + $refund->parent, + ) : + array(); + } + + /** + * The email properties that can be edited. + * + * @return array + */ + protected function get_editable_properties(): array { + return array( + 'content', + 'subject', + 'status', + 'recipient', + ); + } + + /** + * Gets the default email content. + * + * @since 3.3.0 + * @return string + */ + private function get_default_content() { + /* translators: %s: The email tag that will be replaced with the order ID. */ + return sprintf( __( 'Order %s has been refunded.', 'easy-digital-downloads' ), '{payment_id}' ); + } +} diff --git a/src/Emails/Templates/EmailTemplate.php b/src/Emails/Templates/EmailTemplate.php new file mode 100644 index 00000000000..ea2628fba75 --- /dev/null +++ b/src/Emails/Templates/EmailTemplate.php @@ -0,0 +1,489 @@ +email = $email; + } + } + + /** + * Get the default value for a property. + * + * @since 3.3.0 + * @param string $property The property to get the default value for. + * @return mixed + */ + public function get_default( $property ) { + $defaults = $this->defaults(); + + return array_key_exists( $property, $defaults ) ? $defaults[ $property ] : ''; + } + + /** + * Magic getter. + * + * @param string $key The email property to retrieve. + * @return mixed|null + */ + public function __get( $key ) { + + if ( 'status' === $key ) { + return (bool) $this->is_enabled(); + } + + if ( 'preview_data' === $key ) { + return $this->set_preview_data(); + } + + $email = $this->get_email(); + if ( property_exists( $email, $key ) && ! is_null( $email->{$key} ) ) { + return $email->{$key}; + } + + if ( is_callable( array( $this, "get_{$key}" ) ) ) { + return $this->{"get_{$key}"}(); + } + + if ( property_exists( $this, $key ) && ! is_null( $this->{$key} ) ) { + return $this->{$key}; + } + + $legacy = $this->get_legacy( $key ); + if ( $legacy ) { + return $legacy; + } + + return $this->get_default( $key ); + } + + /** + * Determines whether an email property can be edited. + * + * @since 3.3.0 + * @param string $key The email property to check. + * @return bool + */ + public function can_edit( $key ): bool { + if ( 'status' === $key && ! $this->are_base_requirements_met() ) { + return false; + } + + return in_array( $key, $this->get_editable_properties(), true ); + } + + /** + * Gets the email context as a label. + * This is an optional function to allow for more descriptive labels. + * + * @since 3.3.0 + * @return string + */ + public function get_context_label(): string { + return $this->context; + } + + /** + * Gets the content for the status tooltip, if needed. + * + * @since 3.3.0 + * @return array + */ + public function get_status_tooltip(): array { + if ( $this->can_edit( 'status' ) ) { + return array(); + } + + $content = __( 'This email cannot be disabled.', 'easy-digital-downloads' ); + if ( ! $this->is_enabled() ) { + $content = __( 'This email cannot be enabled.', 'easy-digital-downloads' ); + } + + return array( + 'content' => $content, + 'dashicon' => 'dashicons-lock', + ); + } + + /** + * If a tag is required, this will add it to the email tags for the editor + * if it's not already present. A tag can be added, but not required, just by using the + * `get_required_tag_parameters` method. This must contain a label and description, and a tag + * if the required tag is not set. + * + * @return void + */ + final public function maybe_add_required_tag() { + $tag = $this->get_required_tag_parameters(); + if ( empty( $tag ) ) { + return; + } + if ( $this->required_tag && EDD()->email_tags->email_tag_exists( $this->required_tag ) ) { + return; + } + $description = $tag['description']; + if ( ! empty( $this->required_tag ) ) { + $description .= ' ' . __( 'This tag is required for this email.', 'easy-digital-downloads' ); + } + EDD()->email_tags->add( + $tag['tag'] ?? $this->required_tag, + $description, + '__return_true', + $tag['label'], + array( $this->context ) + ); + } + + /** + * Gets the email metadata for a specific key. + * + * @since 3.3.0 + * @param string $key The metadata key. + * @return mixed + */ + public function get_metadata( $key ) { + $email = $this->get_email(); + if ( $email->id && metadata_exists( 'edd_email', $email->id, $key ) ) { + return edd_get_email_meta( $email->id, $key, true ); + } + + $meta = $this->meta; + if ( isset( $meta[ $key ] ) ) { + return $meta[ $key ]; + } + + return $this->get_default( $key ); + } + + /** + * The email properties that can be edited. + * + * @return array + */ + protected function get_editable_properties(): array { + return array( + 'content', + 'subject', + 'heading', + 'status', + ); + } + + /** + * Determines whether the email is enabled. + * + * @since 3.3.0 + * @return bool + */ + protected function is_enabled(): bool { + if ( ! $this->are_base_requirements_met() ) { + return false; + } + + $email = $this->get_email(); + + return (bool) $email->status; + } + + /** + * Determines whether the email's base requirements are met. + * Most emails will not need this. + * + * @since 3.3.0 + * @return bool + */ + public function are_base_requirements_met(): bool { + return true; + } + + /** + * Gets the email object. + * + * @since 3.3.0 + * @return EDD\Emails\Email + */ + public function get_email() { + if ( ! $this->email ) { + $this->email = $this->get_email_from_db(); + } + + return $this->email; + } + + /** + * Adds the email to the database. + * + * @since 3.3.0 + * @return false|int + */ + public function install() { + if ( ! $this->can_view ) { + return false; + } + + if ( $this->installed && $this->get_email()->id ) { + return false; + } + + $email_id = edd_add_email( $this->get_email_data_for_installer() ); + if ( empty( $email_id ) ) { + return false; + } + $this->install_metadata( $email_id ); + + if ( $this->has_legacy_data() ) { + SingleEvent::add( + time() + 30 * DAY_IN_SECONDS, + 'edd_email_legacy_data_cleanup', + array( $email_id ) + ); + } + + $this->installed = true; + + return $email_id; + } + + /** + * Determines whether the email supports HTML. + * + * @since 3.3.4 + * @return bool + */ + public function supports_html(): bool { + return 'text/html' === EDD()->emails->get_content_type(); + } + + /** + * Gets the required tag parameters for the email editor. + * Most emails will not need this. For those that do, just + * return an array with a label and description. + * + * @since 3.3.0 + * @return array|false + */ + protected function get_required_tag_parameters() { + return false; + } + + /** + * Gets the email from the database. + * If the email does not exist, it will be installed. + * + * @since 3.3.0 + * @return EDD\Emails\Email + */ + private function get_email_from_db() { + $email = false; + if ( $this->installed ) { + $email = edd_get_email( $this->email_id ); + } + if ( $email ) { + return $email; + } + + // If the email is not installed, install it. + $this->installed = false; + $email_id = $this->install(); + if ( $email_id ) { + return edd_get_email_by( 'id', $email_id ); + } + + // If the email could not be installed, create a new email object. + return new Email( $this->email_id ); + } + + /** + * Gets the email data for the installer. + * + * @since 3.3.0 + * @return array + */ + private function get_email_data_for_installer() { + return array( + 'email_id' => $this->email_id, + 'subject' => $this->get_legacy( 'subject' ), + 'heading' => $this->get_legacy( 'heading' ), + 'content' => $this->get_legacy( 'content' ), + 'status' => $this->get_legacy( 'status' ), + 'context' => $this->context, + 'sender' => $this->sender, + 'recipient' => $this->recipient, + ); + } + + + /** + * Installs metadata for the specified email template. + * + * @param int $email_id The ID of the email template. + * @return void + */ + private function install_metadata( $email_id ) { + + // Install legacy options meta. + foreach ( $this->get_options() as $option ) { + edd_add_email_meta( $email_id, 'legacy', $option ); + } + + $data = array(); + if ( empty( $this->meta ) ) { + return; + } + + foreach ( $this->meta as $key => $value ) { + if ( ! empty( $value ) ) { + $data[ $key ] = $value; + continue; + } + + if ( is_callable( array( $this, "get_{$key}" ) ) ) { + $value = $this->{"get_{$key}"}(); + if ( ! empty( $value ) ) { + $data[ $key ] = $value; + continue; + } + } + + $legacy_value = $this->get_legacy( $key ); + if ( $legacy_value ) { + $data[ $key ] = $legacy_value; + continue; + } + + $default_value = $this->get_default( $key ); + if ( $default_value ) { + $data[ $key ] = $default_value; + } + } + + if ( empty( $data ) ) { + return; + } + + foreach ( $data as $key => $value ) { + edd_update_email_meta( $email_id, $key, $value ); + } + } +} diff --git a/src/Emails/Templates/NewUser.php b/src/Emails/Templates/NewUser.php new file mode 100644 index 00000000000..b659987afa8 --- /dev/null +++ b/src/Emails/Templates/NewUser.php @@ -0,0 +1,123 @@ + sprintf( _x( '[%s] Your username and password', 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', 'easy-digital-downloads' ), '{sitename}' ), + 'heading' => __( 'Your account info', 'easy-digital-downloads' ), + 'content' => $this->get_default_content(), + 'status' => 1, + ); + } + + /** + * The default body. + * + * @since 3.3.0 + * @return string + */ + private function get_default_content() { + $login_url = apply_filters( 'edd_user_registration_email_login_url', wp_login_url() ); + + $message = sprintf( + /* translators: %s: Email tag that will be replaced with the username */ + _x( + 'Username: %s', + 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', + 'easy-digital-downloads' + ), + '{username}' + ) . "\r\n"; + + $message .= __( 'Password: [entered on site]', 'easy-digital-downloads' ) . "\r\n"; + + if ( EDD()->emails->html ) { + $message .= ' ' . esc_attr__( 'Click here to log in', 'easy-digital-downloads' ) . ' →'; + $message .= "\r\n"; + } else { + /* translators: %s: login URL */ + $message .= sprintf( __( 'To log in, visit: %s', 'easy-digital-downloads' ), esc_url( $login_url ) ) . "\r\n"; + } + + return $message; + } +} diff --git a/src/Emails/Templates/NewUserAdmin.php b/src/Emails/Templates/NewUserAdmin.php new file mode 100644 index 00000000000..95fca88876e --- /dev/null +++ b/src/Emails/Templates/NewUserAdmin.php @@ -0,0 +1,121 @@ + sprintf( _x( '[%s] New User Registration', 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', 'easy-digital-downloads' ), '{sitename}' ), + 'heading' => __( 'New user registration', 'easy-digital-downloads' ), + 'content' => $this->get_default_content(), + 'status' => 1, + ); + } + + /** + * The default content. + * + * @since 3.3.0 + * @return string + */ + private function get_default_content() { + $admin_message = sprintf( + /* translators: %s: Email tag that will be replaced with the username */ + _x( + 'Username: %s', + 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', + 'easy-digital-downloads' + ), + '{username}' + ) . "\r\n\r\n"; + + $admin_message .= sprintf( + /* translators: %s: Email tag that will be replaced with the user email */ + _x( + 'E-mail: %s', + 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', + 'easy-digital-downloads' + ), + '{user_email}' + ) . "\r\n"; + + return $admin_message; + } +} diff --git a/src/Emails/Templates/OrderReceipt.php b/src/Emails/Templates/OrderReceipt.php new file mode 100644 index 00000000000..0361ce26463 --- /dev/null +++ b/src/Emails/Templates/OrderReceipt.php @@ -0,0 +1,127 @@ +{tag}
    markers.', 'easy-digital-downloads' ); + } + + /** + * Define the default email properties. + * + * @since 3.3.0 + * @return array + */ + public function defaults(): array { + return array( + 'subject' => __( 'Purchase Receipt', 'easy-digital-downloads' ), + 'heading' => __( 'Purchase Receipt', 'easy-digital-downloads' ), + 'content' => $this->get_default_content(), + 'status' => 1, + ); + } + + /** + * The email properties that can be edited. + * + * @return array + */ + protected function get_editable_properties(): array { + return array( + 'content', + 'subject', + 'heading', + 'status', + ); + } + + /** + * Gets the default email body content. + * + * @since 3.3.0 + * @return string + */ + private function get_default_content() { + /* translators: %s: Email tag that will be replaced with the customer name */ + $content = sprintf( _x( 'Dear %s,', 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', 'easy-digital-downloads' ), '{name}' ) . "\n\n"; + $content .= __( 'Thank you for your purchase. Please click on the link(s) below to download your files.', 'easy-digital-downloads' ) . "\n\n"; + $content .= '{download_list}' . "\n\n"; + $content .= '{sitename}'; + + return $content; + } + + /* Legacy properties */ + /** + * Gets the option names for this email. + * + * @since 3.3.0 + * @return array + */ + protected function get_options(): array { + return array( + 'content' => 'purchase_receipt', + 'subject' => 'purchase_subject', + 'heading' => 'purchase_heading', + ); + } +} diff --git a/src/Emails/Templates/OrderRefund.php b/src/Emails/Templates/OrderRefund.php new file mode 100644 index 00000000000..dc583b95184 --- /dev/null +++ b/src/Emails/Templates/OrderRefund.php @@ -0,0 +1,134 @@ + __( 'Your order has been refunded', 'easy-digital-downloads' ), + 'content' => $this->get_default_content(), + 'status' => 0, + ); + } + + /** + * Gets the email preview data. + * + * @since 3.3.0 + * @return array + */ + protected function get_preview_data() { + $refund_id = Previews\Data::get_refund_id(); + $refund = edd_get_order( $refund_id ); + + return $refund ? + array( + $refund, + $refund->parent, + ) : + array(); + } + + /** + * The email properties that can be edited. + * + * @return array + */ + protected function get_editable_properties(): array { + return array( + 'content', + 'subject', + 'status', + ); + } + + /** + * Gets the default email content. + * + * @since 3.3.0 + * @return string + */ + private function get_default_content() { + /* translators: %s: Email tag that will be replaced with the customer name */ + $content = sprintf( _x( 'Dear %s,', 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', 'easy-digital-downloads' ), '{name}' ) . "\n\n"; + $content .= __( 'Your order has been refunded.', 'easy-digital-downloads' ); + + return $content; + } +} diff --git a/src/Emails/Templates/PasswordReset.php b/src/Emails/Templates/PasswordReset.php new file mode 100644 index 00000000000..4b44baceed7 --- /dev/null +++ b/src/Emails/Templates/PasswordReset.php @@ -0,0 +1,224 @@ + $this->get_default_content(), + 'subject' => $this->get_default_subject(), + ); + } + + /** + * Determines whether the email's base requirements are met. + * Most emails will not need this. + * + * @since 3.3.0 + * @return bool + */ + public function are_base_requirements_met(): bool { + return edd_get_login_page_uri(); + } + + /** + * Determines whether the email supports HTML. + * + * @since 3.3.4 + * @return false + */ + public function supports_html(): bool { + return false; + } + + /** + * The email editable properties. + * + * @since 3.3.0 + * @return array + */ + protected function get_editable_properties(): array { + return array( 'content' ); + } + + /** + * Whether the email is enabled. + * + * @since 3.3.0 + * @return bool + */ + protected function is_enabled(): bool { + return $this->are_base_requirements_met() && apply_filters( 'send_retrieve_password_email', true ); + } + + /** + * The email context label. + * + * @since 3.3.0 + * @return string + */ + public function get_context_label(): string { + return __( 'Lost Password', 'easy-digital-downloads' ); + } + + /** + * Gets the content for the status tooltip, if needed. + * + * @since 3.3.0 + * @return array + */ + public function get_status_tooltip(): array { + $content = __( 'This email cannot be disabled because it is managed and sent by WordPress.', 'easy-digital-downloads' ); + if ( ! $this->are_base_requirements_met() ) { + $content = __( 'This email cannot be enabled because the login page has not been set.', 'easy-digital-downloads' ); + } elseif ( ! $this->is_enabled() ) { + $content = __( 'This email cannot be enabled because it has been disabled by code.', 'easy-digital-downloads' ); + } + + return array( + 'content' => $content, + 'dashicon' => 'dashicons-lock', + ); + } + + /** + * Gets the required tag parameters for the email editor. + * + * @since 3.3.0 + * @return array + */ + protected function get_required_tag_parameters() { + return array( + 'label' => __( 'Password Reset URL', 'easy-digital-downloads' ), + 'description' => __( 'The link for the user to reset their password.', 'easy-digital-downloads' ), + ); + } + + /** + * The default content. + * + * @since 3.3.0 + * @return string + */ + private function get_default_content() { + $message = __( 'Someone has requested a password reset for the following account:', 'easy-digital-downloads' ) . "\r\n\r\n"; + /* translators: %s: Email tag that will be replaced with the site name */ + $message .= sprintf( __( 'Site Name: %s', 'easy-digital-downloads' ), '{sitename}' ) . "\r\n\r\n"; + $message .= sprintf( + /* translators: %s: Email tag that will be replaced with the username */ + _x( + 'Username: %s', + 'Context: This is an email tag (placeholder) that will be replaced at the time of sending the email', + 'easy-digital-downloads' + ), + '{username}' + ) . "\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 .= '{password_reset_link}'; + $message .= "\r\n\r\n"; + + $message .= sprintf( + /* translators: %s: IP address of password reset requester. */ + __( 'This password reset request originated from the IP address %s.', 'easy-digital-downloads' ), + '{ip_address}' + ) . "\r\n"; + + return $message; + } + + /** + * The email subject. + * + * @since 3.3.0 + * @return string + */ + private function get_default_subject() { + 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 ); + } + /* translators: %s: Site name. */ + $title = sprintf( __( '[%s] Password Reset', 'easy-digital-downloads' ), $site_name ); + + // This is a WP Core filter. + return apply_filters( 'retrieve_password_title', $title, '', new \WP_User( false ) ); + } +} diff --git a/src/Emails/Templates/Previews/Data.php b/src/Emails/Templates/Previews/Data.php new file mode 100644 index 00000000000..5ca7db1496b --- /dev/null +++ b/src/Emails/Templates/Previews/Data.php @@ -0,0 +1,134 @@ + 10, + 'type' => 'sale', + 'status__in' => edd_get_complete_order_statuses(), + 'fields' => 'ids', + ) + ); + + self::$order_id = ! empty( $orders ) ? array_rand( array_flip( $orders ) ) : false; + } + + return self::$order_id; + } + + /** + * Gets a refunded order ID. + * + * @since 3.3.0 + * @return int + */ + public static function get_refund_id() { + if ( ! self::current_user_can() ) { + return false; + } + + if ( is_null( self::$refund_id ) ) { + $orders = edd_get_orders( + array( + 'number' => 10, + 'type' => 'refund', + 'status' => 'complete', + 'fields' => 'ids', + ) + ); + + self::$refund_id = ! empty( $orders ) ? array_rand( array_flip( $orders ) ) : false; + } + + return self::$refund_id; + } + + /** + * Gets a user and related data. + * + * @since 3.3.0 + * @return array + */ + public static function get_user_id_and_data() { + if ( ! self::current_user_can() ) { + return false; + } + + if ( is_null( self::$user_id ) ) { + self::$user_id = get_current_user_id(); + self::$user = get_user_by( 'id', self::$user_id ); + } + + return array( + self::$user_id, + (array) self::$user->data, + ); + } + + /** + * Determines if the current user has the capability to perform a specific action. + * + * @since 3.3.0 + * @return bool True if the current user has the capability, false otherwise. + */ + protected static function current_user_can() { + return current_user_can( 'manage_shop_settings' ); + } +} diff --git a/src/Emails/Templates/Registry.php b/src/Emails/Templates/Registry.php new file mode 100644 index 00000000000..03dc589bab3 --- /dev/null +++ b/src/Emails/Templates/Registry.php @@ -0,0 +1,224 @@ +emails ) ) { + $emails = array( + 'order_receipt' => OrderReceipt::class, + 'admin_order_notice' => AdminOrderNotice::class, + 'order_refund' => OrderRefund::class, + 'admin_order_refund' => AdminOrderRefund::class, + 'new_user' => NewUser::class, + 'new_user_admin' => NewUserAdmin::class, + 'user_verification' => UserVerification::class, + 'password_reset' => PasswordReset::class, + 'stripe_early_fraud_warning' => StripeEarlyFraudWarning::class, + ); + + $this->emails = apply_filters( 'edd_email_registered_templates', $emails ); + } + + return $this->emails; + } + + /** + * Retrieves a list of available recipients. + * + * @since 3.3.0 + * @return array + */ + public function get_recipients() { + $recipients = apply_filters( 'edd_email_recipients', array() ); + + $recipients['customer'] = __( 'Customer', 'easy-digital-downloads' ); + $recipients['admin'] = __( 'Admin', 'easy-digital-downloads' ); + $recipients['user'] = __( 'User', 'easy-digital-downloads' ); + asort( $recipients ); + + return $recipients; + } + + /** + * Retrieves a list of available senders. + * + * @since 3.3.0 + * @return array + */ + public function get_senders() { + $senders = apply_filters( 'edd_email_senders', array() ); + + $senders['edd'] = __( 'EDD Core', 'easy-digital-downloads' ); + $senders['wp'] = __( 'WordPress', 'easy-digital-downloads' ); + asort( $senders ); + + return $senders; + } + + /** + * Retrieves a list of available contexts. + * + * @since 3.3.0 + * @return array + */ + public function get_contexts() { + $contexts = apply_filters( 'edd_email_contexts', array() ); + $contexts = wp_parse_args( + $contexts, + array( + 'order' => __( 'Order', 'easy-digital-downloads' ), + 'refund' => __( 'Refund', 'easy-digital-downloads' ), + 'user' => __( 'Account', 'easy-digital-downloads' ), + ) + ); + + return $contexts; + } + + /** + * Retrieves a list of available actions. + * + * @since 3.3.0 + * @return array + */ + public function get_add_new_actions() { + $actions = array( + 'license_new' => array( + 'promo' => 4916, + 'label' => __( 'Add License Renewal Notice', 'easy-digital-downloads' ), + ), + 'sub_new' => array( + 'promo' => 28530, + 'label' => __( 'Add Subscription Reminder', 'easy-digital-downloads' ), + ), + 'edd_ppe_new' => array( + 'promo' => 90781, + 'label' => __( 'Add Per Product Email', 'easy-digital-downloads' ), + ), + ); + + return apply_filters( 'edd_email_add_new_actions', $actions ); + } + + /** + * Retrieves an `EmailTemplate` instance by its ID (slug). + * + * @since 3.3.0 + * + * @param string $id The email ID. + * @param Email $email Optional. The email object, if already instantiated. + * + * @return EmailTemplate + * @throws Exception If the email class cannot be instantiated. + */ + public function get_email_by_id( $id, $email = null ) { + + $emails = $this->get_emails(); + + // Check the database first. + if ( ! $email instanceof Email ) { + $email = edd_get_email( $id ); + } + if ( $email && array_key_exists( $email->email_id, $emails ) ) { + return $this->make_email_class( $emails[ $email->email_id ], array( $email->email_id, $email ) ); + } + + // Otherwise, loop through the array of emails and try to find it. + foreach ( $emails as $key => $email ) { + try { + $email = $this->make_email_class( $email, array( $key ) ); + if ( $email->email_id === $id ) { + return $email; + } + } catch ( Exception $e ) { + // Do nothing. + } + } + + return null; + } + + /** + * Retrieve an `EmailTemplate` instance by its class name. + * + * @param string $class_name Name of the class. + * @param array $arguments Optional arguments for the class. + * @return EmailTemplate + * @throws Exception If the class is not registered. + */ + public function get_email( $class_name, $arguments = array() ) { + if ( ! in_array( $class_name, $this->get_emails(), true ) ) { + throw new Exception( sprintf( 'Email template %s not found.', $class_name ) ); + } + + return $this->make_email_class( $class_name, $arguments ); + } + + /** + * Converts the supplied `Email` class name into an instance of that class + * (with some validation). + * + * @since 3.3.0 + * @param string $class_name The class name. + * @param array $arguments Optional. The array of arguments. + * @return \EDD\Emails\EmailTemplate + * @throws Exception If the class does not exist or does not extend the `EmailTemplate` class. + */ + public function make_email_class( $class_name, $arguments = array() ) { + $class_name = sanitize_text_field( $class_name ); + if ( ! class_exists( $class_name ) ) { + throw new Exception( __( 'Invalid email template.', 'easy-digital-downloads' ) ); + } + + if ( ! is_subclass_of( $class_name, EmailTemplate::class ) ) { + /* translators: %1$s is the class name, %2$s is the parent class name. */ + throw new Exception( sprintf( __( 'The %1$s class must extend the %2$s class.', 'easy-digital-downloads' ), $class_name, EmailTemplate::class ) ); + } + + return new $class_name( ...$arguments ); + } +} diff --git a/src/Emails/Templates/StripeEarlyFraudWarning.php b/src/Emails/Templates/StripeEarlyFraudWarning.php new file mode 100644 index 00000000000..dc76c1d945b --- /dev/null +++ b/src/Emails/Templates/StripeEarlyFraudWarning.php @@ -0,0 +1,179 @@ + '', + ); + + /** + * Name of the template. + * + * @since 3.3.0 + * @return string + */ + public function get_name() { + return __( 'Stripe Early Fraud Warning', 'easy-digital-downloads' ); + } + + /** + * Description of the email. + * + * @since 3.3.0 + * @return string + */ + public function get_description() { + return __( + "Be alerted when an early fraud warning is detected by Stripe's machine learning. Avoid disputes before they even happen by reviewing flagged orders to verify them.", + 'easy-digital-downloads' + ); + } + + /** + * Define the default email properties. + * + * @since 3.3.0 + * @return array + */ + public function defaults(): array { + return array( + /* translators: %s: Email tag that will be replaced with the order ID. */ + 'subject' => sprintf( __( 'Stripe Early Fraud Warning - Order #%s', 'easy-digital-downloads' ), '{payment_id}' ), + 'heading' => __( 'Possible Fraudulent Order', 'easy-digital-downloads' ), + 'content' => $this->get_default_content(), + 'status' => edd_is_gateway_active( 'stripe' ) ? 1 : 0, + ); + } + + /** + * Gets the content for the status tooltip, if needed. + * + * @since 3.3.0 + * @return array + */ + public function get_status_tooltip(): array { + if ( $this->can_edit( 'status' ) ) { + return array(); + } + + return array( + 'content' => __( 'This email is only available if the Stripe gateway is enabled and using the Payment Elements mode.', 'easy-digital-downloads' ), + 'dashicon' => 'dashicons-lock', + ); + } + + /** + * This email cannot be activated if the Stripe gateway is not active. + * + * @since 3.3.0 + * @return bool + */ + public function are_base_requirements_met(): bool { + return edd_is_gateway_active( 'stripe' ) && 'payment-elements' === edds_get_elements_mode(); + } + + /** + * The email properties that can be edited. + * + * @return array + */ + protected function get_editable_properties(): array { + return array( + 'content', + 'subject', + 'heading', + 'status', + 'recipient', + ); + } + + /** + * Gets the default email content. + * + * @since 3.3.0 + * @return string + */ + private function get_default_content() { + /* translators: %s: The plural label for the Download post type. */ + $content = __( 'Hello', 'easy-digital-downloads' ); + $content .= "\n\n"; + $content .= __( 'Stripe has detected a potential fraudulent order.', 'easy-digital-downloads' ); + $content .= "\n\n"; + /* translators: %s: The plural label for the Download post type. */ + $content .= sprintf( __( '%s sold:', 'easy-digital-downloads' ), edd_get_label_plural() ) . "\n\n"; + $content .= '{download_list}' . "\n\n"; + /* translators: %s: The email tag that will be replaced by the customer's full name */ + $content .= sprintf( __( 'Purchased by: %s', 'easy-digital-downloads' ), '{fullname}' ) . "\n"; + /* translators: %s: The email tag that will be replaced by the order total. */ + $content .= sprintf( _x( 'Amount: %s', 'Context: This is a tag (placholder) for email content that will be replaced when sending.', 'easy-digital-downloads' ), '{price}' ) . "\n"; + /* translators: 1: The opening anchor tag, 2: The closing anchor tag */ + $content .= sprintf( __( '%1$sOrder Details%2$s', 'easy-digital-downloads' ), '', '' ) . "\n\n"; + $content .= __( 'Note: Once you have reviewed the order, ensure you take the appropriate action within your Stripe dashboard to help improve future fraud detection.', 'easy-digital-downloads' ); + + return $content; + } +} diff --git a/src/Emails/Templates/TemplateInterface.php b/src/Emails/Templates/TemplateInterface.php new file mode 100644 index 00000000000..b2676e38483 --- /dev/null +++ b/src/Emails/Templates/TemplateInterface.php @@ -0,0 +1,38 @@ + 'edd-emails', + 'email' => $this->email_id, + ) + ); + } + + /** + * Retrieves the row actions for this email. + * + * @since 3.3.0 + * @return array + */ + public function get_row_actions() { + $row_actions = array( + 'edit' => array( + 'url' => $this->get_edit_url(), + 'text' => __( 'Edit', 'easy-digital-downloads' ), + ), + ); + + if ( $this->can_preview() ) { + $row_actions['view'] = array( + 'url' => wp_nonce_url( + add_query_arg( + array( + 'edd_action' => 'preview_email', + 'email' => $this->email_id, + ), + home_url() + ), + 'edd-preview-email' + ), + 'text' => __( 'Preview', 'easy-digital-downloads' ), + 'target' => '_blank', + ); + } + if ( $this->can_test() ) { + $row_actions['test'] = array( + 'url' => wp_nonce_url( + add_query_arg( + array( + 'edd-action' => 'send_test_email', + 'email' => $this->email_id, + ) + ), + 'edd-test-email' + ), + 'text' => __( 'Send Test', 'easy-digital-downloads' ), + ); + } + + return $row_actions; + } +} diff --git a/src/Emails/Templates/Traits/Legacy.php b/src/Emails/Templates/Traits/Legacy.php new file mode 100644 index 00000000000..82271d68e98 --- /dev/null +++ b/src/Emails/Templates/Traits/Legacy.php @@ -0,0 +1,97 @@ +get_options() as $option ) { + if ( array_key_exists( $option, $edd_options ) ) { + return true; + } + } + + return false; + } + + /** + * Removes the legacy options from `edd_settings`. + * + * @since 3.3.0 + * @return void + */ + public function remove_legacy_data() { + if ( ! $this->get_email()->id ) { + return; + } + + foreach ( $this->get_options() as $option ) { + edd_delete_option( $option ); + } + } + + /** + * Gets the option names for this email. + * + * @since 3.3.0 + * @return array + */ + protected function get_options(): array { + return array(); + } + + /** + * Gets a legacy option. + * + * @since 3.3.0 + * @param string $key The email template key. + * @return mixed + */ + protected function get_legacy( $key ) { + $option = $this->get_option_name( $key ); + if ( ! $option ) { + if ( $this->installed ) { + return false; + } + + return $this->get_default( $key ); + } + if ( 'status' === $key ) { + return (bool) empty( edd_get_option( $this->get_option_name( 'disabled' ), false ) ); + } + + return stripslashes( edd_get_option( $option, $this->get_default( $key ) ) ); + } + + /** + * Gets the option name for a setting, if it exists. + * + * @since 3.3.0 + * @param string $key The option key. + * @return string|false + */ + private function get_option_name( $key ) { + $options = $this->get_options(); + if ( 'status' === $key ) { + $key = 'disabled'; + } + + return array_key_exists( $key, $options ) && ! empty( $options[ $key ] ) ? $options[ $key ] : false; + } +} diff --git a/src/Emails/Templates/Traits/Previews.php b/src/Emails/Templates/Traits/Previews.php new file mode 100644 index 00000000000..88f15c71d9f --- /dev/null +++ b/src/Emails/Templates/Traits/Previews.php @@ -0,0 +1,125 @@ +preview_data ) ) { + $this->preview_data = $this->get_preview_data(); + } + + return $this->preview_data; + } + + /** + * Check if we are doing a preview or test. + * + * @since 3.3.0 + * @return bool + */ + final public static function doing_preview() { + $edd_action = filter_input( INPUT_GET, 'edd_action', FILTER_SANITIZE_SPECIAL_CHARS ); + if ( empty( $edd_action ) ) { + $edd_action = filter_input( INPUT_GET, 'edd-action', FILTER_SANITIZE_SPECIAL_CHARS ); + } + + return ! empty( $edd_action ) && in_array( $edd_action, array( 'preview_email', 'send_test_email' ), true ); + } + + /** + * Generates the preview data for this email. + * + * @since 3.3.0 + * @return array + */ + protected function get_preview_data() { + if ( 'order' === $this->context ) { + return array( + edd_get_order( Data::get_complete_order_id() ), + ); + } + + if ( 'user' === $this->context ) { + return Data::get_user_id_and_data(); + } + + if ( 'refund' === $this->context ) { + $refund_id = Data::get_refund_id(); + + return array( + edd_get_order( $refund_id->parent ), + $refund_id, + ); + } + + return array( false ); + } + + /** + * Whether the email can be previewed. + * This requires valid preview data. + * + * @since 3.3.0 + * @return bool + */ + protected function can_preview() { + if ( ! $this->can_preview || ! \EDD\Emails\Registry::is_registered( $this->email_id ) ) { + return false; + } + + return $this->has_preview_data(); + } + + /** + * Whether a test email can be sent. + * This requires valid preview data. + * + * @since 3.3.0 + * @return bool + */ + protected function can_test() { + + if ( ! $this->can_test || ! \EDD\Emails\Registry::is_registered( $this->email_id ) ) { + return false; + } + + return $this->has_preview_data(); + } + + /** + * Whether the email has valid preview data. + * + * @since 3.3.0 + * @return bool + */ + private function has_preview_data(): bool { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return false; + } + + if ( is_null( $this->preview_data ) ) { + $this->preview_data = $this->get_preview_data(); + } + + if ( is_array( $this->preview_data ) ) { + return ! empty( array_filter( $this->preview_data ) ); + } + + return ! empty( $this->preview_data ); + } +} diff --git a/src/Emails/Templates/UserVerification.php b/src/Emails/Templates/UserVerification.php new file mode 100644 index 00000000000..1b14504e4d8 --- /dev/null +++ b/src/Emails/Templates/UserVerification.php @@ -0,0 +1,152 @@ + __( 'Verify your account', 'easy-digital-downloads' ), + 'heading' => __( 'Verify your account', 'easy-digital-downloads' ), + 'content' => $this->get_body_default(), + 'status' => 1, + ); + } + + /** + * Gets the required tag parameters for the email editor. + * + * @since 3.3.0 + * @return array + */ + protected function get_required_tag_parameters() { + return array( + 'label' => __( 'Verification URL', 'easy-digital-downloads' ), + 'description' => __( 'The link for the user to verify their account.', 'easy-digital-downloads' ), + ); + } + + /** + * Retrieves the preview data for this email. + * + * @since 3.3.0 + * @return array + */ + protected function get_preview_data() { + return array( get_current_user_id() ); + } + + /** + * The email properties that can be edited. + * + * @return array + */ + protected function get_editable_properties(): array { + return array( + 'content', + 'subject', + 'heading', + ); + } + + /** + * The default email body. + * + * @since 3.3.0 + * @return string + */ + private function get_body_default() { + $message = sprintf( + /* translators: %s: The email tag that will be replaced with the customer's full name. */ + __( 'Hello %s,', 'easy-digital-downloads' ), + '{fullname}', + ) . "\n\n"; + $message .= sprintf( + /* translators: %s: The email tag that will be replaced with the Site Name. */ + __( 'Your account with %s needs to be verified before you can access your order history.', 'easy-digital-downloads' ), + '{sitename}', + ) . "\n\n"; + $message .= sprintf( + /* translators: %s: The email tag that will be replaced with the verification URL. */ + __( 'Visit this link to verify your account: %s', 'easy-digital-downloads' ), + '{verification_url}', + ) . "\n\n"; + + return $message; + } +} diff --git a/src/Emails/Traits/Orders.php b/src/Emails/Traits/Orders.php new file mode 100644 index 00000000000..081f507dcaa --- /dev/null +++ b/src/Emails/Traits/Orders.php @@ -0,0 +1,113 @@ +type || 'complete' !== $order->status ) { + return; + } + + $this->send_receipt( $order ); + $this->send_admin_notice( $order ); + } + + /** + * Send the order receipt. + * + * @param Order $order The order object. + * @return void + */ + private function send_receipt( Order $order ) { + if ( ! metadata_exists( 'edd_order', $order->id, '_edd_should_send_order_receipt' ) ) { + _edd_deprecated_function( 'edd_trigger_purchase_receipt', '3.2.0', 'EDD\Emails\Types\OrderReceipt' ); + + return; + } + + $should_send_order_receipt = edd_get_order_meta( $order->id, '_edd_should_send_order_receipt', true ); + + edd_debug_log( 'order_receipt should send: ' . var_export( $should_send_order_receipt, true ) ); + + if ( $should_send_order_receipt ) { + // Send the email. + $order_receipt = Registry::get( 'order_receipt', array( $order ) ); + $order_receipt->send(); + } + + // Delete the meta so we don't keep it around. + edd_delete_order_meta( $order->id, '_edd_should_send_order_receipt' ); + } + + /** + * Send the admin order notice. + * + * @param Order $order The order object. + * @return void + */ + private function send_admin_notice( Order $order ) { + if ( ! metadata_exists( 'edd_order', $order->id, '_edd_should_send_admin_order_notice' ) ) { + _edd_deprecated_function( 'edd_admin_email_notice', '3.2.0', 'EDD\Emails\Types\AdminOrderNotice' ); + + return; + } + + // To know if people unhooked the legacy filter on edd_purchase_complete, check the order meta. + $should_send_admin_order_notice = edd_get_order_meta( $order->id, '_edd_should_send_admin_order_notice', true ); + + edd_debug_log( 'admin_order_notice should send: ' . var_export( $should_send_admin_order_notice, true ) ); + + if ( $should_send_admin_order_notice ) { + // Send the email. + $admin_notice = Registry::get( 'admin_order_notice', array( $order ) ); + $admin_notice->send(); + } + + // Delete the meta so we don't keep it around. + edd_delete_order_meta( $order->id, '_edd_should_send_admin_order_notice' ); + } +} diff --git a/src/Emails/Traits/Preview.php b/src/Emails/Traits/Preview.php new file mode 100644 index 00000000000..8ce477ddca9 --- /dev/null +++ b/src/Emails/Traits/Preview.php @@ -0,0 +1,127 @@ +get_preview_data( $id ) ); + + // Set this as a preview email. + $test->is_test = true; + + // For tests, disable links and send. + add_filter( 'edd_email_show_links', '__return_false' ); + $sent = $test->send(); + remove_filter( 'edd_email_show_links', '__return_false' ); + + $edd_message = $sent ? 'test-email-sent' : 'test-email-failed'; + + $redirect = array( + 'page' => 'edd-emails', + 'edd-message' => $edd_message, + ); + if ( ! empty( $data['editor'] ) ) { + $redirect['email'] = $data['email']; + } + + edd_redirect( edd_get_admin_url( $redirect ) ); + } + + /** + * Preview an email. + * + * This previously ran on `template_redirect` but now runs on it's own EDD Action, to avoid always running it. + * + * @since 3.2.0 + * @param array $data The $_GET data. + * @return void + */ + public function preview_email( $data ) { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + // Verify the nonce. + if ( empty( $data['_wpnonce'] ) || ! wp_verify_nonce( $data['_wpnonce'], 'edd-preview-email' ) ) { + return; + } + + $email_id = ! empty( $data['email'] ) ? sanitize_text_field( $data['email'] ) : 'order_receipt'; + $preview = Registry::get( $email_id, $this->get_preview_data( $email_id ) ); + $preview->is_preview = true; + + echo $preview->get_preview(); + + exit; + } + + /** + * Get the preview parameters for the email. + * + * @since 3.3.0 + * @param string $email_id The email ID. + * @return array + */ + private function get_preview_data( $email_id ) { + $template = $this->get_template( $email_id ); + + return $template ? $template->set_preview_data() : array(); + } + + /** + * Gets the email template. + * + * @since 3.3.0 + * @param string $email_id The email ID. + * @return \EDD\Emails\Templates\EmailTemplate + */ + private function get_template( $email_id ) { + return edd_get_email_registry()->get_email_by_id( $email_id ); + } + + /** + * Send the test purchase confirmation email. + * + * This does the work of verifying the nonce and checking the capabilities. + * + * @since 3.2.0 + * @deprecated 3.3.0 + * @param array $data The $_POST data. + */ + public function send_test_order_receipt( $data ) { + $this->send_test_email( $data ); + } +} diff --git a/src/Emails/Triggers.php b/src/Emails/Triggers.php new file mode 100644 index 00000000000..c656bc4465b --- /dev/null +++ b/src/Emails/Triggers.php @@ -0,0 +1,181 @@ + array( 'send_order_emails', 9999, 3 ), // Run this late, so that other plugins can modify the order before the email is sent. + 'edd_email_links' => array( 'resend_order_receipt', 10, 1 ), + 'edd_send_test_email' => array( 'send_test_email', 10, 1 ), + 'edd_preview_email' => array( 'preview_email', 10 ), + 'edd_refund_order' => array( 'send_refund_receipt', 10, 2 ), + 'edd_insert_user' => array( 'send_new_user_email', 10, 2 ), + 'edd_stripe_early_fraud_warning' => array( 'send_stripe_early_fraud_warning', 10, 1 ), + ); + } + + /** + * Resend the order receipt. + * + * @since 3.2.0 + * + * @param array $data The $_POST data. + * + * @return void + */ + public function resend_order_receipt( $data ) { + $order_id = absint( $data['purchase_id'] ); + + if ( empty( $order_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'] ) : ''; + $order = edd_get_order( $order_id ); + + if ( empty( $email ) ) { + $customer = edd_get_customer( $order->customer_id ); + $email = $customer->email; + } + + $order_receipt = Registry::get( 'order_receipt', array( $order ) ); + $order_receipt->send_to = $email; + $sent = $order_receipt->send(); + + // Allow filtering this as extensions like Per Product Emails may disable sending the main receipt. + $sent = apply_filters( 'edd_resend_order_receipt_was_sent', $sent, $order, $email ); + + // If the email was sent, reset the file download limits. + if ( false !== $sent ) { + if ( is_array( $order->order_items ) ) { + foreach ( $order->order_items as $order_item ) { + $limit = edd_get_file_download_limit( $order_item->product_id ); + if ( ! empty( $limit ) ) { + edd_set_file_download_limit_override( $order_item->product_id, $order_id ); + } + } + } + } + + switch ( $sent ) { + case true: + $edd_message = 'email_sent'; + break; + case false: + $edd_message = 'email_send_failed'; + break; + case null: + $edd_message = 'email_possibly_not_sent'; + break; + } + + edd_redirect( + add_query_arg( + array( + 'edd-message' => $edd_message, + 'edd-action' => false, + 'purchase_id' => false, + 'email' => false, + ) + ) + ); + } + + /** + * Send the refund receipt. + * + * @since 3.3.0 + * @param int $order_id The order ID. + * @param int $refund_id The refund ID. + * + * @return void + */ + public function send_refund_receipt( $order_id, $refund_id ) { + if ( ! $refund_id ) { + return; + } + $refund = edd_get_order( $refund_id ); + if ( $refund && 'refund' === $refund->type ) { + $refund_notice = Registry::get( 'order_refund', array( $refund, $order_id ) ); + $refund_notice->send(); + + $admin_notice = Registry::get( 'admin_order_refund', array( $refund, $order_id ) ); + $admin_notice->send(); + } + } + + /** + * Send the new user email. + * + * @since 3.3.0 + * @param int $user_id The user ID. + * @param array $user_data The user data. + * + * @return void + */ + public function send_new_user_email( $user_id, $user_data ) { + if ( empty( $user_id ) || empty( $user_data ) ) { + return; + } + + $user_email = Registry::get( 'new_user', array( $user_id, $user_data ) ); + $user_email->send(); + $admin_email = Registry::get( 'new_user_admin', array( $user_id, $user_data ) ); + $admin_email->send(); + } + + /** + * Register the emails. + * + * @since 3.2.0 + * @deprecated 3.3.0 + * @return void + */ + public function register_emails() {} + + /** + * Send the Stripe Early Fraud Warning email. + * + * @since 3.3.0 + * @param EDD\Orders\Order $order The order object. + * + * @return void + */ + public function send_stripe_early_fraud_warning( $order ) { + $early_fraud_warning = Registry::get( 'stripe_early_fraud_warning', array( $order ) ); + $early_fraud_warning->send(); + } +} diff --git a/src/Emails/Types/AdminOrderNotice.php b/src/Emails/Types/AdminOrderNotice.php new file mode 100644 index 00000000000..981d47cc434 --- /dev/null +++ b/src/Emails/Types/AdminOrderNotice.php @@ -0,0 +1,283 @@ +order = $order; + $this->order_id = false !== $order ? $order->id : 0; + + // Setup any of the legacy filters we need to run. + $this->set_legacy_filters(); + } + + /** + * Set the raw email body content. + * + * This will add the email content to the `raw_body_content` property. It has not yet had + * the tag replacements executed. + * + * @since 3.2.0 + * @return void + */ + protected function set_email_body_content() { + $this->raw_body_content = $this->get_email()->content; + + $this->maybe_run_legacy_filter( 'edd_sale_notification' ); + + $this->raw_body_content = apply_filters( 'edd_admin_order_notification', $this->raw_body_content, $this->order ); + } + + /** + * Set the email from name. + * + * @since 3.2.0 + * @return void + */ + protected function set_from_name() { + parent::set_from_name(); + + $this->maybe_run_legacy_filter( 'edd_purchase_from_name' ); + + $this->from_name = apply_filters( 'edd_order_admin_notice_from_name', $this->from_name, $this->order ); + } + + /** + * Set the email from address. + * + * @since 3.2.0 + * @return void + */ + protected function set_from_email() { + parent::set_from_email(); + + $this->maybe_run_legacy_filter( 'edd_admin_sale_from_address' ); + + $this->from_email = apply_filters( 'edd_order_admin_notice_from_email', $this->from_email, $this->order ); + } + + /** + * Set the email to address. + * + * @since 3.2.0 + * @return void + */ + protected function set_to_email() { + $this->send_to = $this->get_email()->get_admin_recipient_emails( $this->order ); + } + + /** + * Set the email headers. + * + * @since 3.2.0 + * @return void + */ + protected function set_headers() { + parent::set_headers(); + + $this->maybe_run_legacy_filter( 'edd_admin_sale_notification_headers' ); + + $this->headers = apply_filters( 'edd_order_admin_notice_headers', $this->headers, $this->order ); + } + + /** + * Set the email subject. + * + * @since 3.2.0 + * @return void + */ + protected function set_subject() { + parent::set_subject(); + + $this->maybe_run_legacy_filter( 'edd_admin_sale_notification_subject' ); + + $this->subject = apply_filters( 'edd_order_admin_notice_subject', $this->subject, $this->order ); + $this->subject = $this->process_tags( $this->subject, $this->order_id, $this->order ); + } + + /** + * Set the email heading. + * + * @since 3.2.0 + * @return void + */ + protected function set_heading() { + parent::set_heading(); + + $this->maybe_run_legacy_filter( 'edd_admin_sale_notification_heading' ); + + $this->heading = apply_filters( 'edd_order_admin_notice_heading', $this->heading, $this->order ); + $this->heading = $this->process_tags( $this->heading, $this->order_id, $this->order ); + } + + /** + * Set the email message. + * + * @since 3.2.0 + * @return void + */ + protected function set_message() { + parent::set_message(); + + // We don't want admins to get the users download links, so we'll set edd_email_show_links to false. + add_filter( 'edd_email_show_links', '__return_false' ); + $this->message = $this->process_tags( $this->message, $this->order_id, $this->order ); + remove_filter( 'edd_email_show_links', '__return_false' ); + } + + /** + * Set the email attachments. + * + * @since 3.2.0 + * @return void + */ + protected function set_attachments() { + $this->attachments = array(); + + $this->maybe_run_legacy_filter( 'edd_admin_sale_notification_attachments' ); + + $this->attachments = apply_filters( 'edd_order_admin_notice_attachments', $this->attachments, $this->order ); + } + + /** + * Allows filtering to disable sending the admin sale notification. + * + * @since 3.2.0 + * + * @return bool + */ + protected function should_send() { + + // Emails should not be sent for imported orders. + if ( edd_get_order_meta( $this->order->id, '_edd_imported', true ) ) { + return false; + } + + if ( ! parent::should_send() ) { + return false; + } + + // Allows disabling this email by filter. + if ( true === apply_filters( 'edd_disable_' . $this->id, false, $this ) ) { + return false; + } + + return true; + } + + /** + * Set the used legacy filters. + * + * These filters all expect legacy EDD_Payment meta to be passed in, so we need to check + * them if they are being used. We store this locally so we can check it later. + * + * @since 3.2.0 + * + * @return void + */ + protected function set_legacy_filters() { + $this->legacy_filters = array( + 'edd_sale_notification' => array( + 'has_filter' => has_filter( 'edd_sale_notification' ), + 'property' => 'raw_body_content', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_purchase_from_name' => array( + 'has_filter' => has_filter( 'edd_purchase_from_name' ), + 'property' => 'from_name', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_admin_sale_from_address' => array( + 'has_filter' => has_filter( 'edd_admin_sale_from_address' ), + 'property' => 'from_email', + 'arguments' => array( 'order_id' ), + ), + 'edd_admin_sale_notification_heading' => array( + 'has_filter' => has_filter( 'edd_admin_sale_notification_heading' ), + 'property' => 'heading', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_admin_sale_notification_subject' => array( + 'has_filter' => has_filter( 'edd_admin_sale_notification_subject' ), + 'property' => 'subject', + 'arguments' => array( 'order_id' ), + ), + 'edd_admin_sale_notification_attachments' => array( + 'has_filter' => has_filter( 'edd_admin_sale_notification_attachments' ), + 'property' => 'headers', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_admin_sale_notification_headers' => array( + 'has_filter' => has_filter( 'edd_admin_sale_notification_headers' ), + 'property' => 'raw_body_content', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + ); + } +} diff --git a/src/Emails/Types/AdminOrderRefund.php b/src/Emails/Types/AdminOrderRefund.php new file mode 100644 index 00000000000..288bd3f2a09 --- /dev/null +++ b/src/Emails/Types/AdminOrderRefund.php @@ -0,0 +1,101 @@ +refund = $refund; + $this->order_id = $order_id; + $this->order = edd_get_order( $order_id ); + } + + /** + * Set the email to address. + * + * @since 3.3.0 + * @return void + */ + protected function set_to_email() { + $this->send_to = $this->get_email()->get_admin_recipient_emails( $this->order ); + } + + /** + * Set the email message. + * + * @since 3.3.0 + * @return void + */ + protected function set_message() { + parent::set_message(); + + $this->message = $this->process_tags( $this->message, $this->order_id, $this->order ); + $this->message = $this->process_tags( $this->message, $this->refund->id, $this->refund ); + } +} diff --git a/src/Emails/Types/Email.php b/src/Emails/Types/Email.php new file mode 100644 index 00000000000..b798efe1f4f --- /dev/null +++ b/src/Emails/Types/Email.php @@ -0,0 +1,660 @@ +$property ) ) { + return $this->$property; + } + + if ( is_callable( array( $this, 'get_' . $property ) ) ) { + return call_user_func( array( $this, 'get_' . $property ) ); + } + + return null; + } + + /** + * Get the email ID. + * + * @since 3.2.0 + * + * @return string + */ + public function get_id() { + return $this->id; + } + + /** + * Get the email context. + * + * @since 3.2.0 + * + * @return string + */ + public function get_context() { + return $this->context; + } + + /** + * Return the raw body content. + * + * This will contain the email tags, in an non-replaced format. + * + * @since 3.2.0 + * @return string + */ + public function get_raw_body_content() { + if ( ! $this->raw_body_content ) { + $this->set_email_body_content(); + } + + return $this->raw_body_content; + } + + /** + * Get the email recipient type. + * + * @since 3.2.0 + * + * @return string + */ + public function get_recipient_type() { + return $this->recipient_type; + } + + /** + * Send the email. + * + * @since 3.2.0 + * @return bool + */ + public function send() { + // If we shouldn't send the email, just return. No `false` value here, as that could produce failure emails when it shouldn't. + if ( ! $this->should_send() && ! $this->is_test ) { + // Return early, so we don't spend any processing time on an email we are not going to send. + return null; + } + + // If we should send the email, we can go ahead and ensure it is built. + if ( ! $this->is_built ) { + $this->build(); + } + + // For test emails, we always send to the admin email. + if ( $this->is_test ) { + $this->send_to = $this->get_test_recipient(); + } + + $this->set_email_properties(); + + edd_debug_log( 'Sending email: ' . $this->id ); + + $sent = $this->processor()->send( $this->send_to, $this->subject, $this->message, $this->attachments ); + + $this->log_email( $sent ); + + do_action( 'edd_email_sent_' . $this->id, $this, $sent ); + + return $sent; + } + + /** + * Returns the final email content that would be sent, after templates have been applied. + * + * @since 3.2.0 + */ + public function get_preview() { + if ( ! $this->is_preview ) { + return ''; + } + + if ( empty( $this->message ) ) { + add_filter( 'edd_email_show_links', '__return_false' ); + $this->set_message(); + remove_filter( 'edd_email_show_links', '__return_false' ); + } + + return $this->processor()->build_email( $this->message ); + } + + /** + * Get the processing class EDD\Emails\Base. + * + * @since 3.2.0 + * @return Base + */ + protected function processor() { + if ( is_null( $this->processor ) ) { + $this->processor = new Base(); + } + + return $this->processor; + } + + /** + * Build the email. + * + * @since 3.2.0 + * @return void + */ + protected function build() { + edd_load_email_tags(); + + $this->set_from_name(); + + $this->set_from_email(); + + $this->set_to_email(); + + $this->set_headers(); + + $this->set_subject(); + + $this->set_heading(); + + $this->set_message(); + + $this->set_attachments(); + + $this->is_built = true; + } + + /** + * Set the email body content, without tags replaced. + * + * @since 3.2.0 + * @return void + */ + protected function set_email_body_content() { + $this->raw_body_content = $this->get_email()->content; + } + + /** + * Set the email from name. + * + * @since 3.2.0 + * @return void + */ + protected function set_from_name() { + $this->from_name = edd_get_option( 'from_name', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ); + } + + /** + * Set the email from address. + * + * @since 3.2.0 + * @return void + */ + protected function set_from_email() { + $this->from_email = edd_get_option( 'from_email', get_option( 'admin_email' ) ); + } + + /** + * Set the email to address. + * + * @since 3.2.0 + * @return void + */ + abstract protected function set_to_email(); + + /** + * Set the email headers. + * + * @since 3.2.0 + * @return void + */ + protected function set_headers() { + $this->headers = $this->processor()->get_headers(); + } + + /** + * Set the email subject. + * + * @since 3.2.0 + * @return void + */ + protected function set_subject() { + $this->subject = $this->get_email()->subject; + } + + /** + * Set the email heading. + * + * @since 3.2.0 + * @return void + */ + protected function set_heading() { + $this->heading = $this->get_email()->heading; + } + + /** + * Set the email message. + * In this class, this function only gets the message and maybe applies wpautop. + * Tags are processed in the child classes. + * + * @since 3.2.0 + * @return void + */ + protected function set_message() { + $this->message = $this->maybe_apply_autop( $this->get_raw_body_content() ); + } + + /** + * Set the email attachments. + * + * @since 3.2.0 + * @return void + */ + protected function set_attachments() { + $this->attachments = array(); + } + + /** + * Possibly runs the content provided through wpautop. + * + * @since 3.2.0 + * + * @param string $content The content to run through wpautop. + * + * @return string + */ + protected function maybe_apply_autop( $content ) { + if ( apply_filters( 'edd_email_template_wpautop', true ) ) { + return wpautop( $content ); + } + + return $content; + } + + /** + * Process the tags in the content. + * + * @since 3.2.0 + * + * @param string $content The content to process. + * @param int $object_id The object ID to pass to the tags. + * @param object $email_object The email object to pass to the tags. + * @param string $context Optional: The context to pass to the tags. + * + * @return string + */ + protected function process_tags( $content, $object_id, $email_object = null, $context = null ) { + $process_object = false; + if ( ! $context ) { + $context = $this->get_context(); + $process_object = true; + } + if ( 'order' === $context && ( $this->is_preview || $this->is_test ) ) { + $content = edd_email_preview_template_tags( $content, false, $object_id ); + } + + // Preferred usage: sends the email object to the tags, if a custom context was not passed in. + if ( $process_object ) { + $content = EDD()->email_tags->do_tags( $content, $object_id, $email_object, $this ); + } + + // Original usage: sends the email context (order, subscription, user) to the tags. + return edd_do_email_tags( $content, $object_id, $email_object, $context ); + } + + /** + * Should this email send. + * + * This is a placeholder method that should be overridden in the child class if there is logic that needs to be run + * on if this email should be sent or not. + * + * @since 3.2.0 + * + * @return bool + */ + protected function should_send() { + $should_send = (bool) $this->get_email() && $this->get_email()->is_enabled(); + + return apply_filters( 'edd_should_send_email_' . $this->id, $should_send, $this ); + } + + /** + * Add properties to the base email processor. + * + * @since 3.2.0 + * @return void + */ + private function set_email_properties() { + $this->processor()->__set( 'from_name', $this->from_name ); + $this->processor()->__set( 'from_address', $this->from_email ); + $this->processor()->__set( 'heading', $this->heading ); + $this->processor()->__set( 'headers', $this->headers ); + } + + /** + * Get the default email body content. + * + * @since 3.2.0 + * @deprecated 3.3.0 Deprecated in favor of instantiating the settings classes directly. + * @return string + */ + protected function get_default_body_content() { + return ''; + } + + /** + * Get the email object. + * + * @since 3.3.0 + * @return \EDD\Emails\Email + */ + protected function get_email() { + if ( ! $this->email ) { + $this->email = $this->get_email_from_db(); + } + + return $this->email; + } + + /** + * Gets the email template settings. + * + * @since 3.3.0 + * @return \EDD\Admin\Emails\Templates\EmailTemplate + */ + protected function get_template() { + if ( ! $this->template ) { + $email = $this->get_email(); + $this->template = $email->get_template(); + } + + return $this->template; + } + + /** + * Gets the test recipient. + * + * @since 3.3.0 + * @return string + */ + protected function get_test_recipient() { + return get_option( 'admin_email' ); + } + + /** + * Gets the email object ID. + * + * @since 3.3.0 + * @return int|false + */ + protected function get_email_object_id() { + if ( ! empty( $this->email_object_id ) ) { + return $this->email_object_id; + } + + $object_id = false; + $reflection = new \ReflectionClass( $this ); + $constructor = $reflection->getConstructor(); + $parameters = $constructor->getParameters(); + if ( empty( $parameters ) ) { + return false; + } + $first_parameter = reset( $parameters ); + $name = $first_parameter->getName(); + if ( is_numeric( $this->{$name} ) ) { + $object_id = $this->{$name}; + } elseif ( ! empty( $this->{$name} ) && ! empty( $this->{$name}->id ) ) { + $object_id = $this->{$name}->id; + } + + $this->email_object_id = (int) $object_id; + + return $this->email_object_id; + } + + /** + * Logs the email. + * + * @since 3.3.0 + * @param bool $sent Whether or not the email was sent. + * @return int|false + */ + private function log_email( $sent ) { + if ( ! $sent ) { + return false; + } + if ( 'admin' === $this->get_recipient_type() ) { + return false; + } + if ( $this->is_test ) { + return false; + } + $object_id = $this->get_email_object_id(); + if ( ! $object_id ) { + return false; + } + $logs = new LogEmail(); + + return $logs->add_item( + array( + 'object_id' => $object_id, + 'object_type' => $this->context, + 'email_id' => $this->id, + 'subject' => $this->subject, + 'email' => $this->send_to, + ) + ); + } + + /** + * Get the email from the database. + * If the email cannot be found, a new email object is returned. + * + * @since 3.3.0 + * @return \EDD\Emails\Email + */ + private function get_email_from_db() { + $email = edd_get_email( $this->id ); + if ( $email ) { + return $email; + } + + $email_template = edd_get_email_registry()->get_email_by_id( $this->id, $this ); + if ( $email_template ) { + $id = $email_template->install(); + + return edd_get_email_by( 'id', $id ); + } + + $email = new \EDD\Emails\Email(); + $email->email_id = $this->id; + + return $email; + } +} diff --git a/src/Emails/Types/LegacyPaymentFilters.php b/src/Emails/Types/LegacyPaymentFilters.php new file mode 100644 index 00000000000..ce9fb145d6c --- /dev/null +++ b/src/Emails/Types/LegacyPaymentFilters.php @@ -0,0 +1,120 @@ + array( + * 'has_filter' => has_filter( 'filter_hook' ), + * 'property' => 'property_filter_changes', + * 'arguments' => array( 'property_one', 'property_two' ), + * ), + * ); + * + * @see EDD\Emails\Types\OrderReceipt::set_legacy_filters() for an example. + * + * @since 3.2.0 + */ + abstract protected function set_legacy_filters(); + + /** + * Maybe run a legacy filter. + * + * If the legacy filter is run, the property it modifies will be updated. + * + * @since 3.2.0 + * + * @param string $hook The hook to check. + * + * @return void + */ + private function maybe_run_legacy_filter( $hook = '' ) { + if ( ! $this->is_legacy_filter_used( $hook ) ) { + return; + } + + $filter_properties = $this->legacy_filters[ $hook ]; + + $property = $filter_properties['property']; + $value = $this->$property; + $arguments = array(); + foreach ( $filter_properties['arguments'] as $argument ) { + // Ensure we set the payment data, only if we need it. + if ( 'payment_meta' === $argument ) { + $this->set_payment_data(); + } + + $arguments[] = $this->$argument; + } + + $this->$property = apply_filters_ref_array( $hook, array_merge( array( $value ), $arguments ) ); + } + + /** + * Check if a legacy filter is being used. + * + * @since 3.2.0 + * @param string $filter The filter to check. + * + * @return bool + */ + private function is_legacy_filter_used( $filter = '' ) { + if ( empty( $filter ) ) { + return false; + } + + return array_key_exists( $filter, $this->legacy_filters ) && $this->legacy_filters[ $filter ]['has_filter']; + } + + /** + * Sets the payment object on the class. + * + * @since 3.2.0 + * + * @return void + */ + private function set_payment_data() { + if ( $this->payment_object instanceof \EDD_Payment && ! empty( $this->payment_meta ) ) { + return; + } + + $this->payment_object = new \EDD_Payment( $this->order_id ); + $this->payment_meta = $this->payment_object->get_meta( '_edd_payment_meta', true ); + } +} diff --git a/src/Emails/Types/NewUser.php b/src/Emails/Types/NewUser.php new file mode 100644 index 00000000000..dcd9065a9e1 --- /dev/null +++ b/src/Emails/Types/NewUser.php @@ -0,0 +1,175 @@ +user_id = $user_id; + $this->user_data = $user_data; + } + + /** + * Set the email body content, without tags replaced. + * + * @since 3.3.0 + * @return void + */ + protected function set_email_body_content() { + $content = $this->apply_legacy_filters( $this->get_email()->content, 'message' ); + + /** + * Filter the email body content. + * + * @since 3.3.7 + * @param string $content The email body content. + * @param \WP_User $user The user object. + */ + $content = apply_filters( 'edd_new_user_email_message', $content, get_userdata( $this->user_id ) ); + + $this->raw_body_content = $content; + } + + /** + * Set the email to address. + * + * @since 3.3.0 + * @return void + */ + protected function set_to_email() { + if ( empty( $this->send_to ) ) { + $this->send_to = $this->user_data['user_email']; + } + } + + /** + * Set the email subject. + * + * @since 3.3.0 + * @return void + */ + protected function set_subject() { + parent::set_subject(); + + $this->subject = $this->apply_legacy_filters( $this->subject, 'subject' ); + $this->subject = $this->process_tags( $this->subject, $this->user_id ); + } + + /** + * Set the email heading. + * + * @since 3.3.0 + * @return void + */ + protected function set_heading() { + parent::set_heading(); + $this->heading = $this->apply_legacy_filters( $this->heading, 'heading' ); + + $this->heading = $this->process_tags( $this->heading, $this->user_id ); + } + + /** + * Set the email message. + * + * @since 3.3.0 + * @return void + */ + protected function set_message() { + parent::set_message(); + + $this->message = $this->process_tags( $this->message, $this->user_id ); + } + + /** + * Apply legacy filters. + * + * @param string $content The content to filter. + * @param string $key The key to get the filter name. + * @return string + */ + private function apply_legacy_filters( $content, $key ) { + $legacy_filters = array( + 'message' => 'edd_user_registration_email_message', + 'subject' => 'edd_user_registration_email_subject', + 'heading' => 'edd_user_registration_email_heading', + ); + + if ( ! array_key_exists( $key, $legacy_filters ) ) { + return $content; + } + + if ( ! has_filter( $legacy_filters[ $key ] ) ) { + return $content; + } + + EDD()->notifications->maybe_add_local_notification( + array( + 'remote_id' => 'new_user_mail_filter', + 'buttons' => '', + 'conditions' => '', + 'type' => 'warning', + 'title' => __( 'Please Check Your Custom Code', 'easy-digital-downloads' ), + 'content' => __( 'EDD has detected that you are filtering the user registration email. To improve security and performance, in an upcoming release of EDD, the password will no longer be included in the email filter. Please verify and update any customizations you may have in place.', 'easy-digital-downloads' ), + ) + ); + + return apply_filters( $legacy_filters[ $key ], $content, $this->user_data ); + } +} diff --git a/src/Emails/Types/NewUserAdmin.php b/src/Emails/Types/NewUserAdmin.php new file mode 100644 index 00000000000..91b706083d6 --- /dev/null +++ b/src/Emails/Types/NewUserAdmin.php @@ -0,0 +1,127 @@ +user_id = $user_id; + $this->user_data = $user_data; + } + + /** + * Set the email body content, without tags replaced. + * + * @since 3.3.0 + * @return void + */ + protected function set_email_body_content() { + $this->raw_body_content = apply_filters( 'edd_user_registration_admin_email_message', $this->get_email()->content, $this->user_data ); + } + + /** + * Set the email to address. + * + * @since 3.3.0 + * @return void + */ + protected function set_to_email() { + $this->send_to = get_option( 'admin_email' ); + } + + /** + * Set the email subject. + * + * @since 3.3.0 + * @return void + */ + protected function set_subject() { + parent::set_subject(); + + $this->subject = apply_filters( 'edd_user_registration_admin_email_subject', $this->subject, $this->user_data ); + $this->subject = $this->process_tags( $this->subject, $this->user_id ); + } + + /** + * Set the email heading. + * + * @since 3.3.0 + * @return void + */ + protected function set_heading() { + parent::set_heading(); + $this->heading = apply_filters( 'edd_user_registration_admin_email_heading', $this->heading, $this->user_data ); + + $this->heading = $this->process_tags( $this->heading, $this->user_id ); + } + + /** + * Set the email message. + * + * @since 3.3.0 + * @return void + */ + protected function set_message() { + parent::set_message(); + + $this->message = apply_filters( 'edd_user_registration_admin_email_message', $this->message, $this->user_data ); + $this->message = $this->process_tags( $this->message, $this->user_id ); + } +} diff --git a/src/Emails/Types/OrderReceipt.php b/src/Emails/Types/OrderReceipt.php new file mode 100644 index 00000000000..13ce98eab2b --- /dev/null +++ b/src/Emails/Types/OrderReceipt.php @@ -0,0 +1,317 @@ +order = $order; + + // To support previews, we need to possibly set a `0` order ID. + $this->order_id = false !== $order ? $order->id : 0; + + // Since we are refactoring this, we need to set the legacy filters. + $this->set_legacy_filters(); + } + + /** + * Get the order of the emails. + * + * @since 3.2.0 + * + * @return EDD\Orders\Order The order object. + */ + public function get_order() { + return $this->order; + } + + /** + * Set the raw email body content. + * + * This will add the email content to the `raw_body_content` property. It has not yet had + * the tag replacements executed. + * + * @since 3.2.0 + * @return void + */ + protected function set_email_body_content() { + parent::set_email_body_content(); + + $this->maybe_run_legacy_filter( 'edd_purchase_receipt_' . $this->processor()->get_template() ); + $this->maybe_run_legacy_filter( 'edd_purchase_receipt' ); + + $this->raw_body_content = apply_filters( 'edd_email_template_wpautop', true ) ? wpautop( $this->raw_body_content ) : $this->raw_body_content; + $this->raw_body_content = apply_filters( 'edd_order_receipt_' . $this->processor()->get_template(), $this->raw_body_content, $this->order ); + $this->raw_body_content = apply_filters( 'edd_order_receipt', $this->raw_body_content, $this->order ); + } + + /** + * Set the 'from' name on the email. + * + * @since 3.2.0 + * @return void + */ + protected function set_from_name() { + parent::set_from_name(); + + $this->maybe_run_legacy_filter( 'edd_purchase_from_name' ); + + $this->from_name = apply_filters( 'edd_order_receipt_from_name', $this->from_name, $this->order ); + } + + /** + * Set the 'from' email on the email. + * + * @since 3.2.0 + * @return void + */ + protected function set_from_email() { + parent::set_from_email(); + + $this->maybe_run_legacy_filter( 'edd_purchase_from_address' ); + + $this->from_email = apply_filters( 'edd_order_receipt_from_email', $this->from_email, $this->order ); + } + + /** + * Set the 'to' email on the email. + * + * @since 3.2.0 + * @return void + */ + protected function set_to_email() { + if ( empty( $this->send_to ) && ! empty( $this->order->email ) ) { + $this->send_to = $this->order->email; + } + } + + /** + * Set the headers email on the email. + * + * @since 3.2.0 + * @return void + */ + protected function set_headers() { + parent::set_headers(); + + $this->maybe_run_legacy_filter( 'edd_receipt_headers' ); + + $this->headers = apply_filters( 'edd_order_receipt_headers', $this->headers, $this->order ); + } + + /** + * Set the subject on the email. + * + * @since 3.2.0 + * @return void + */ + protected function set_subject() { + parent::set_subject(); + + $this->maybe_run_legacy_filter( 'edd_purchase_subject' ); + + $this->subject = apply_filters( 'edd_order_receipt_email_subject', $this->subject, $this->order ); + $this->subject = $this->process_tags( $this->subject, $this->order_id, $this->order ); + } + + /** + * Set the heading on the email. + * + * @since 3.2.0 + * @return void + */ + protected function set_heading() { + parent::set_heading(); + + $this->maybe_run_legacy_filter( 'edd_purchase_heading' ); + + $this->heading = apply_filters( 'edd_order_receipt_email_heading', wp_strip_all_tags( $this->heading ), $this->order ); + $this->heading = $this->process_tags( $this->heading, $this->order_id, $this->order ); + } + + /** + * Set the message on the email. + * + * @since 3.2.0 + * @return void + */ + protected function set_message() { + parent::set_message(); + + $this->message = $this->process_tags( $this->message, $this->order_id, $this->order ); + } + + /** + * Set the attachments on the email. + * + * @since 3.2.0 + * @return void + */ + protected function set_attachments() { + $this->attachments = array(); + + $this->maybe_run_legacy_filter( 'edd_receipt_attachments' ); + + $this->attachments = apply_filters( 'edd_order_receipt_email_attachments', $this->attachments, $this->order ); + } + + /** + * Allows filtering to disable sending the default order receipt email. + * + * @since 3.2.0 + * + * @return bool + */ + protected function should_send() { + + // Do not send receipts for orders that have been marked as 'imported'. + if ( edd_get_order_meta( $this->order->id, '_edd_imported', true ) ) { + return false; + } + + if ( ! $this->get_email()->is_enabled() ) { + return false; + } + + // Allow developers to unhook this email via a filter. + if ( true === apply_filters( 'edd_disable_' . $this->id, false, $this ) ) { + return false; + } + + return true; + } + + /** + * Set the used legacy filters. + * + * These filters all expect legacy EDD_Payment meta to be passed in, so we need to check + * them if they are being used. We store this locally so we can check it later. + * + * @since 3.2.0 + * + * @return void + */ + protected function set_legacy_filters() { + $email_template = $this->processor()->get_template(); + + $this->legacy_filters = array( + 'edd_purchase_from_name' => array( + 'has_filter' => has_filter( 'edd_purchase_from_name' ), + 'property' => 'from_name', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_purchase_from_address' => array( + 'has_filter' => has_filter( 'edd_purchase_from_address' ), + 'property' => 'from_email', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_purchase_subject' => array( + 'has_filter' => has_filter( 'edd_purchase_subject' ), + 'property' => 'subject', + 'arguments' => array( 'order_id' ), + ), + 'edd_purchase_heading' => array( + 'has_filter' => has_filter( 'edd_purchase_heading' ), + 'property' => 'heading', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_receipt_attachments' => array( + 'has_filter' => has_filter( 'edd_receipt_attachments' ), + 'property' => 'attachments', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_receipt_headers' => array( + 'has_filter' => has_filter( 'edd_receipt_headers' ), + 'property' => 'headers', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_purchase_receipt_' . $email_template => array( + 'has_filter' => has_filter( 'edd_purchase_receipt_' . $email_template ), + 'property' => 'raw_body_content', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + 'edd_purchase_receipt' => array( + 'has_filter' => has_filter( 'edd_purchase_receipt' ), + 'property' => 'raw_body_content', + 'arguments' => array( 'order_id', 'payment_meta' ), + ), + ); + } + + /** + * Gets the test recipient. + * + * @since 3.3.0 + * @return string + */ + protected function get_test_recipient() { + return edd_get_admin_notice_emails(); + } +} diff --git a/src/Emails/Types/OrderRefund.php b/src/Emails/Types/OrderRefund.php new file mode 100644 index 00000000000..e226531a768 --- /dev/null +++ b/src/Emails/Types/OrderRefund.php @@ -0,0 +1,103 @@ +refund = $refund; + $this->order_id = $order_id; + $this->order = edd_get_order( $order_id ); + } + + /** + * Set the email to address. + * + * @since 3.3.0 + * @return void + */ + protected function set_to_email() { + if ( empty( $this->send_to ) && ! empty( $this->order->email ) ) { + $this->send_to = $this->order->email; + } + } + + /** + * Set the email message. + * + * @since 3.3.0 + * @return void + */ + protected function set_message() { + parent::set_message(); + + $this->message = $this->process_tags( $this->message, $this->order_id, $this->order ); + $this->message = $this->process_tags( $this->message, $this->refund->id, $this->refund ); + } +} diff --git a/src/Emails/Types/StripeEarlyFraudWarning.php b/src/Emails/Types/StripeEarlyFraudWarning.php new file mode 100644 index 00000000000..d8a3d41fdd8 --- /dev/null +++ b/src/Emails/Types/StripeEarlyFraudWarning.php @@ -0,0 +1,137 @@ +order = $order; + $this->order_id = false !== $order ? $order->id : 0; + } + + /** + * Set the raw email body content. + * + * This will add the email content to the `raw_body_content` property. It has not yet had + * the tag replacements executed. + * + * @since 3.3.0 + * @return void + */ + protected function set_email_body_content() { + $this->raw_body_content = $this->get_email()->content; + } + + /** + * Set the email to address. + * + * @since 3.3.0 + * @return void + */ + protected function set_to_email() { + $this->send_to = $this->get_email()->get_admin_recipient_emails( $this->order ); + } + + /** + * Set the email subject. + * + * @since 3.3.0 + * @return void + */ + protected function set_subject() { + parent::set_subject(); + + $this->subject = $this->process_tags( $this->subject, $this->order_id, $this->order ); + } + + /** + * Set the heading on the email. + * + * @since 3.3.0 + * @return void + */ + protected function set_heading() { + parent::set_heading(); + + $this->heading = $this->process_tags( $this->heading, $this->order_id, $this->order ); + } + + /** + * Set the email message. + * + * @since 3.3.0 + * @return void + */ + protected function set_message() { + parent::set_message(); + + // We don't want admins to get the users download links, so we'll set edd_email_show_links to false. + add_filter( 'edd_email_show_links', '__return_false' ); + $this->message = $this->process_tags( $this->message, $this->order_id, $this->order ); + remove_filter( 'edd_email_show_links', '__return_false' ); + } +} diff --git a/src/Emails/Types/UserVerification.php b/src/Emails/Types/UserVerification.php new file mode 100644 index 00000000000..746c211906b --- /dev/null +++ b/src/Emails/Types/UserVerification.php @@ -0,0 +1,146 @@ +user_id = $user_id; + $this->user_data = get_userdata( $user_id ); + } + + /** + * Set the email message. + * In this class, this function only gets the message and maybe applies wpautop. + * Tags are processed in the child classes. + * + * @since 3.2.0 + * @return void + */ + protected function set_message() { + parent::set_message(); + if ( false === strpos( $this->message, '{verification_url}' ) ) { + $this->message = $this->get_fallback_content(); + } else { + $this->message = $this->parse_tag( $this->message ); + } + + $this->message = $this->process_tags( $this->message, $this->user_id, $this->user_data ); + $this->message = apply_filters( 'edd_user_verification_email_message', $this->message, $this->user_id ); + } + + /** + * Set the email subject. + * + * @since 3.2.0 + * @return void + */ + protected function set_subject() { + parent::set_subject(); + $this->subject = apply_filters( 'edd_user_verification_email_subject', $this->subject, $this->user_id ); + } + + /** + * Set the email heading. + * + * @since 3.2.0 + * @return void + */ + protected function set_heading() { + parent::set_heading(); + $this->heading = apply_filters( 'edd_user_verification_email_heading', $this->heading, $this->user_id ); + } + + /** + * Set the email to address. + * + * @since 3.2.0 + * @return void + */ + protected function set_to_email() { + $this->send_to = $this->user_data->user_email; + } + + /** + * Gets the original email content, from before this was registered + * as an email template. + * + * @since 3.3.0 + * @return string + */ + private function get_fallback_content() { + if ( empty( $this->from_name ) ) { + $this->set_from_name(); + } + $template = $this->get_template(); + + return $this->parse_tag( $template->get_default( 'content' ) ); + } + + /** + * Parses the {verification_url} tag. + * + * @since 3.3.0 + * @param string $content The content to parse. + * @return string + */ + private function parse_tag( $content ) { + return str_replace( '{verification_url}', esc_url_raw( edd_get_user_verification_url( $this->user_id ) ), $content ); + } +} 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..260e7c60499 --- /dev/null +++ b/src/Extensions/Handler.php @@ -0,0 +1,424 @@ +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() { + + // Don't show the notice if EDD (Pro) is active. + if ( edd_is_pro() ) { + return false; + } + + // 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..94b038bf231 --- /dev/null +++ b/src/Extensions/Updater.php @@ -0,0 +1,661 @@ +get_plugin_file( $_plugin_file ); + $this->api_data = $_api_data; + $this->plugin_file = $_plugin_file; + $this->name = plugin_basename( $_plugin_file ); + $this->slug = basename( dirname( $_plugin_file ) ); + $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; + + $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_limited_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 a limited set of data from the API response. + * This is used for the update_plugins transient. + * + * @since 3.2.10 + * @return \stdClass|false + */ + private function get_limited_data() { + $version_info = $this->get_repo_api_data(); + + if ( ! $version_info ) { + return false; + } + + $limited_data = new \stdClass(); + $limited_data->slug = $this->slug; + $limited_data->plugin = $this->name; + $limited_data->url = $version_info->url; + $limited_data->package = $version_info->package; + $limited_data->icons = $this->convert_object_to_array( $version_info->icons ); + $limited_data->banners = $this->convert_object_to_array( $version_info->banners ); + $limited_data->new_version = $version_info->new_version; + $limited_data->tested = $version_info->tested; + $limited_data->requires = $version_info->requires; + $limited_data->requires_php = $version_info->requires_php; + + return $limited_data; + } + + /** + * Gets the plugin's tested version. + * + * @since 1.9.2 + * @param object $version_info The 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 The plugin file. + * @param array $plugin The plugin data. + */ + 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( + /* translators: 1: opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate 4. opening anchor tag, do not translate 5. closing anchor tag, do not translate. */ + __( '%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 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + + 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 The default data. + * @param string $_action The requested action. + * @param object $_args The Plugin API arguments. + * @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; + } + + if ( ! isset( $_data->version ) && ! empty( $_data->new_version ) ) { + $_data->version = $_data->new_version; + } + + 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 The data to convert. + * + * @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; + } + + return $this->get_version_from_remote(); + } + + /** + * 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() { + + $api_handler = new \EDD\Licensing\API(); + $api_handler->should_check_failed_request = true; + $request = $api_handler->make_request( $this->get_api_params() ); + if ( ! $request ) { + return false; + } + + if ( isset( $request->sections ) ) { + $request->sections = maybe_unserialize( $request->sections ); + } + + 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 The 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|\stdClass $value The value to store. + * @param string $cache_key The 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 ) ); + } + + /** + * Gets the plugin file. This is to correct an incorrect plugin file passed by Stripe 3.0.0 and 3.0.1. + * + * @since 3.2.2 + * @param string $file The plugin file. + * @return string + */ + private function get_plugin_file( $file ) { + + // Return the file if it's not the Stripe plugin with the wrong file. + if ( false === strpos( $file, 'functions.php' ) ) { + return $file; + } + + // Replace the incorrect file path and return the correct string. + if ( false !== strpos( $file, 'edd-stripe/includes/functions.php' ) ) { + return str_replace( 'includes/functions.php', 'edd-stripe.php', $file ); + } + + // Otherwise, return the file as is. + return $file; + } +} diff --git a/src/Fees/Handler.php b/src/Fees/Handler.php new file mode 100644 index 00000000000..0582bd171a7 --- /dev/null +++ b/src/Fees/Handler.php @@ -0,0 +1,350 @@ + 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 ( ! in_array( $args['type'], array( 'fee', 'item' ), true ) ) { + $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 Handler::get_fees() + * @uses EDD\Sessions\Handler::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 Handler::get_fees() + * @return bool True if there are fees, false otherwise + */ + public function has_fees( $type = 'fee' ) { + + if ( in_array( $type, array( 'all', 'fee' ), true ) && ! 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\Sessions\Handler::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 Handler::get_fees() + * @uses Handler::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 Handler::get_fees() + * @uses Handler::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 ); + } + + /** + * Gets the tax to be added to a fee. + * + * @since 3.0 + * @param array $fee The fee to calculate tax for. + * @param float $tax_rate The tax rate to apply. + * @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; + } + + /** + * Stores the fees in the payment meta. + * + * @since 1.5 + * @deprecated 3.3.7 + * @uses EDD\Sessions\Handler::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; + } +} diff --git a/src/Gateways/PayPal/IPN.php b/src/Gateways/PayPal/IPN.php new file mode 100644 index 00000000000..e70c3aa0d55 --- /dev/null +++ b/src/Gateways/PayPal/IPN.php @@ -0,0 +1,681 @@ +posted = $posted; + $this->listen(); + } + + /** + * Listens for an IPN call from PayPal + * + * This is intended to be a 'backup' listener, for if the webhook is no longer connected for a specific PayPal object. + * Events that are handled here: + * - new_case + * - recurring_payment + * - recurring_payment_outstanding_payment + * - recurring_payment_profile_cancel + * - recurring_payment_suspended + * - recurring_payment_suspended_due_to_max_failed_payment + * - recurring_payment_failed + * - recurring_payment_expired + * + * @since 3.1.0.3 + * @since 3.2.0 Moved to a class to allow for better organization. + */ + public function listen() { + if ( empty( $_GET['edd-listener'] ) || 'eppe' !== $_GET['edd-listener'] ) { + return; + } + + // If PayPal is not connected, we don't need to run here. + if ( ! PayPal\has_rest_api_connection() ) { + return; + } + + // If the transaction type is ignored, we don't need to run here. + $ignored_txn_types = array( 'recurring_payment_profile_created' ); + if ( isset( $this->posted['txn_type'] ) && in_array( $this->posted['txn_type'], $ignored_txn_types, true ) ) { + $this->debug_log( 'Transaction Type ' . $this->posted['txn_type'] . ' is ignored by the PayPal Commerce IPN.' ); + return; + } + + $this->debug_log( 'IPN Backup Loaded' ); + + /** + * The is_verified() method returned true if the IPN was "VERIFIED" and false if it was "INVALID". + */ + if ( ! $this->is_verified() ) { + $this->debug_log( 'verification failed, bailing.' ); + status_header( 400 ); + die( 'invalid IPN' ); + } + + status_header( 200 ); + + if ( ! empty( $this->posted['txn_type'] ) ) { + $this->txn_type = strtolower( $this->posted['txn_type'] ); + } + if ( ! empty( $this->posted['payment_status'] ) ) { + $this->payment_status = strtolower( $this->posted['payment_status'] ); + } + + if ( empty( $this->txn_type ) && empty( $this->payment_status ) ) { + $this->debug_log( 'No txn_type or payment_status in the IPN, bailing' ); + return; + } + + $this->amount = $this->get_amount(); + if ( ! empty( $this->posted['mc_currency'] ) ) { + $this->currency_code = $this->posted['mc_currency']; + } elseif ( ! empty( $this->posted['currency_code'] ) ) { + $this->currency_code = $this->posted['currency_code']; + } + if ( ! empty( $this->posted['txn_id'] ) ) { + $this->transaction_id = $this->posted['txn_id']; + } + + if ( 'new_case' === $this->txn_type && ! empty( $this->transaction_id ) ) { + $this->log_dispute(); + return; + } + + // Process webhooks from recurring first, as that is where most of the missing actions will come from. + $this->maybe_handle_recurring(); + + $this->maybe_handle_refunds(); + } + + /** + * Verifies the IPN data with PayPal. + * + * @since 3.2.0 + * @return bool + */ + private function is_verified() { + $encoded_data_array = $this->get_encoded_data_array(); + if ( ! $encoded_data_array ) { + return false; + } + + // In certain cases, we will bypass the verification process. + if ( edd_is_test_mode() || edd_get_option( 'disable_paypal_verification' ) || isset( $this->posted['verification_override'] ) ) { + return true; + } + + $this->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 ) ) { + edd_record_gateway_error( + __( 'IPN Error', 'easy-digital-downloads' ), + /* translators: %s: IPN Verification response */ + sprintf( __( 'Invalid PayPal Commerce/Express IPN verification response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $api_response ) ) + ); + $this->debug_log( 'verification failed. Data: ' . var_export( $body, true ) ); + status_header( 401 ); + return false; // Something went wrong. + } + + if ( 'VERIFIED' !== $body ) { + edd_record_gateway_error( + __( 'IPN Error', 'easy-digital-downloads' ), + /* translators: %s: IPN Verification response */ + sprintf( __( 'Invalid PayPal Commerce/Express IPN verification response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $api_response ) ) + ); + $this->debug_log( 'verification failed. Data: ' . var_export( $body, true ) ); + status_header( 401 ); + return false; // Response not okay. + } + + // We've verified that the IPN Check passed, we can proceed with processing the IPN data sent to us. + return true; + } + + /** + * Gets the encoded data array. + * + * @since 3.2.0 + * @return array|bool + */ + private function get_encoded_data_array() { + + nocache_headers(); + + // 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( $this->posted ) ) { + // Nothing to do. + $this->debug_log( 'post data not detected, bailing' ); + return false; + } + + // Loop through each POST. + foreach ( $this->posted 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 ); + + return $encoded_data_array; + } + + /** + * Note: Amounts get more properly sanitized on insert. + * + * @see EDD_Subscription::add_payment() + * @since 3.2.0 + * @return float + */ + private function get_amount() { + if ( isset( $this->posted['amount'] ) ) { + return (float) $this->posted['amount']; + } + if ( isset( $this->posted['mc_gross'] ) ) { + return (float) $this->posted['mc_gross']; + } + + return 0; + } + + /** + * Logs a dispute. + * + * @since 3.2.0 + * @return void + */ + private function log_dispute() { + $this->debug_log( 'IPN for dispute ' . $this->transaction_id ); + $order_id = edd_get_order_id_from_transaction_id( $this->transaction_id ); + if ( ! $order_id ) { + return; + } + $order = edd_get_order( $order_id ); + if ( ! $order || 'on_hold' === $order->status ) { + return; + } + $dispute_id = ! empty( $this->posted['case_id'] ) ? $this->posted['case_id'] : false; + $reason = ! empty( $this->posted['reason_code'] ) ? $this->posted['reason_code'] : false; + if ( $dispute_id ) { + edd_record_order_dispute( $order_id, $dispute_id, $reason ); + } + edd_add_note( + array( + 'object_type' => 'order', + 'object_id' => $order_id, + 'content' => sprintf( + /* translators: 1: Dispute ID, 2: Dispute reason code. Example: The PayPal transaction has been disputed. Case ID: PP-R-NMW-10060094. Reason given: non_receipt. */ + __( 'The PayPal transaction has been disputed (IPN). Case ID: %1$s. Reason given: %2$s.', 'easy-digital-downloads' ), + $dispute_id, + ! empty( $reason ) ? $reason : __( 'unknown', 'easy-digital-downloads' ) + ), + ) + ); + } + + /** + * Handles recurring payments. + * + * @since 3.2.0 + * @return void + */ + private function maybe_handle_recurring() { + if ( ! class_exists( '\\EDD_Subscription' ) ) { + return; + } + if ( empty( $this->posted['recurring_payment_id'] ) ) { + return; + } + // Allow the posted data to be modified. + $this->posted = apply_filters( 'edd_recurring_ipn_post', $this->posted ); + + $subscription = new \EDD_Subscription( $this->posted['recurring_payment_id'], true ); + + // Bail if this is the very first payment. + if ( ! empty( $this->posted['payment_date'] ) && date( 'Y-n-d', strtotime( $subscription->created ) ) == date( 'Y-n-d', strtotime( $this->posted['payment_date'] ) ) ) { + $this->debug_log( 'IPN for subscription ' . $subscription->id . ': processing stopped because this is the initial payment.' ); + return; + } + + $parent_order = edd_get_order( $subscription->parent_payment_id ); + if ( 'paypal_commerce' !== $parent_order->gateway ) { + $this->debug_log( 'This is not for PayPal Commerce - bailing' ); + return; + } + + if ( empty( $subscription->id ) || $subscription->id < 1 ) { + $this->debug_log( 'no matching subscription found detected, bailing. Data: ' . var_export( $this->posted, true ) ); + die( 'No subscription found' ); + } + + $this->debug_log( 'Processing ' . $this->txn_type . ' IPN for subscription ' . $subscription->id ); + + // Subscriptions. + switch ( $this->txn_type ) : + + case 'recurring_payment': + case 'recurring_payment_outstanding_payment': + $this->process_recurring_payment( $subscription ); + break; + + case 'recurring_payment_profile_cancel': + case 'recurring_payment_suspended': + case 'recurring_payment_suspended_due_to_max_failed_payment': + $this->cancel( $subscription ); + break; + + case 'recurring_payment_failed': + if ( 'failing' === $subscription->status ) { + $this->debug_log( 'Subscription ID ' . $subscription->id . ' already failing.' ); + return; + } + + $subscription->failing(); + $this->debug_log( 'subscription ' . $subscription->id . ': subscription failing.' ); + do_action( 'edd_recurring_payment_failed', $subscription ); + + break; + + case 'recurring_payment_expired': + $this->complete( $subscription ); + break; + + endswitch; + } + + /** + * Gets the order ID from the transaction ID. + * + * @since 3.2.0 + * @return int + */ + private function get_order_id() { + return ! empty( $this->posted['parent_txn_id'] ) ? + edd_get_order_id_from_transaction_id( $this->posted['parent_txn_id'] ) : + 0; + } + + /** + * Processes a recurring payment. + * + * @since 3.2.0 + * @param \EDD_Subscription $subscription The subscription object. + * @return void + */ + private function process_recurring_payment( $subscription ) { + $transaction_exists = edd_get_order_transaction_by( 'transaction_id', $this->transaction_id ); + if ( ! empty( $transaction_exists ) ) { + $this->debug_log( 'Transaction ID ' . $this->transaction_id . ' arlready processed.' ); + return; + } + + // verify details. + if ( ! empty( $parent_order->currency ) && strtolower( $this->currency_code ) !== strtolower( $parent_order->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( $this->posted ) ) ); + + $this->debug_log( 'subscription ' . $subscription->id . ': invalid currency code detected in IPN data: ' . var_export( $this->posted, true ) ); + + die( 'invalid currency code' ); + } + + if ( 'failed' === $this->payment_status ) { + if ( 'failing' === $subscription->status ) { + $this->debug_log( 'Subscription ID ' . $subscription->id . ' arlready failing.' ); + return; + } + + $transaction_link = '' . $this->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(); + + $this->debug_log( 'subscription ' . $subscription->id . ': payment failed in PayPal' ); + + die( 'Subscription payment failed' ); + } + + $this->debug_log( 'subscription ' . $subscription->id . ': preparing to insert renewal payment' ); + + // Build the array for adding a subscription order. + $subscription_payment_args = array( + 'amount' => $this->amount, + 'transaction_id' => $this->transaction_id, + ); + + $payment_date = $this->get_payment_date(); + if ( $payment_date ) { + $subscription_payment_args['date'] = $payment_date; + } + + // when a user makes a recurring payment. + $payment_id = $subscription->add_payment( $subscription_payment_args ); + + if ( ! empty( $payment_id ) ) { + $this->debug_log( 'subscription ' . $subscription->id . ': renewal payment was recorded successfully, preparing to renew subscription' ); + $subscription->renew( $payment_id ); + + if ( 'recurring_payment_outstanding_payment' === $this->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' ), $this->amount ) ); + } + } else { + $this->debug_log( 'subscription ' . $subscription->id . ': renewal payment creation appeared to fail.' ); + } + + die( 'Subscription payment successful' ); + } + + /** + * Cancels a subscription. + * + * @since 3.2.0 + * @param \EDD_Subscription $subscription The subscription object. + * @return void + */ + private function cancel( $subscription ) { + if ( 'cancelled' === $subscription->status ) { + $this->debug_log( 'Subscription ID ' . $subscription->id . ' already cancelled.' ); + return; + } + + $subscription->cancel(); + $this->debug_log( 'subscription ' . $subscription->id . ': subscription cancelled.' ); + + die( 'Subscription cancelled' ); + } + + /** + * Completes a subscription. + * + * @since 3.2.0 + * @param \EDD_Subscription $subscription The subscription object. + * @return void + */ + private function complete( $subscription ) { + if ( 'completed' === $subscription->status ) { + $this->debug_log( 'Subscription ID ' . $subscription->id . ' arlready completed.' ); + return; + } + + $subscription->complete(); + $this->debug_log( 'subscription ' . $subscription->id . ': subscription completed.' ); + + die( 'Subscription completed' ); + } + + /** + * Handles refunds. + * + * @since 3.2.0 + * @return void + */ + private function maybe_handle_refunds() { + // 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( $this->payment_status, $statuses_to_process, true ) ) { + $this->debug_log( 'Payment Status was not a status we need to process: ' . $this->payment_status ); + return; + } + + $order_id = $this->get_order_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 ) ) { + $this->debug_log( 'IPN Track ID ' . $this->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 ) { + $this->debug_log( 'Order ' . $order_id . ' was not with PayPal Commerce' ); + return; + } + + if ( 'refunded' === $order->status ) { + $this->debug_log( 'Order ' . $order_id . ' is already refunded' ); + } + + $transaction_exists = edd_get_order_transaction_by( 'transaction_id', $this->transaction_id ); + if ( ! empty( $transaction_exists ) ) { + $this->debug_log( 'Refund transaction for ' . $this->transaction_id . ' already exists' ); + return; + } + + $order_amount = $order->total; + $refunded_amount = ! empty( $this->amount ) ? $this->amount : $order_amount; + $currency = ! empty( $this->currency_code ) ? $this->currency_code : $order->currency; + + if ( 'reversed' === $this->payment_status ) { + $this->debug_log( 'Processing a reversal for original transaction ' . $order->get_transaction_id() ); + edd_add_note( + array( + 'object_type' => 'order', + 'object_id' => $order->id, + 'content' => sprintf( + /* translators: %s: Transaction ID */ + __( 'Reversal processed in PayPal (IPN). Transaction ID: %s', 'easy-digital-downloads' ), + $this->transaction_id + ), + ) + ); + // The order may already be on hold. If not, we need to record the dispute. + if ( 'on_hold' !== $order->status && empty( edd_get_order_hold_reason( $order->id ) ) ) { + $reason = ! empty( $this->posted['reason_code'] ) ? $this->posted['reason_code'] : ''; + edd_record_order_dispute( $order->id, '', $reason ); + } + + die( 'Reversal processed' ); + } + + $this->debug_log( 'Processing a refund for original transaction ' . $order->get_transaction_id() ); + + $payment_note = sprintf( + /* translators: 1: Amount refunded; %2$s - Original payment ID; %3$s - Refund transaction ID */ + 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( $this->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 (IPN).', '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 (IPN).', 'easy-digital-downloads' ) . ' ' . $payment_note, + ) + ); + $refund_id = edd_refund_order( $order->id ); + if ( $refund_id ) { + edd_add_order_transaction( + array( + 'object_type' => 'order', + 'object_id' => $refund_id, + 'transaction_id' => $this->transaction_id, + 'gateway' => 'paypal_commerce', + 'total' => $refunded_amount, + 'status' => 'complete', + 'currency' => $currency, + ) + ); + } else { + edd_update_order_status( $order->id, 'refunded' ); + } + } + + die( 'Refund processed' ); + } + + /** + * Gets the payment date from the IPN data. + * + * @since 3.2.0 + * @return false|string + */ + private function get_payment_date() { + if ( empty( $this->posted['payment_date'] ) ) { + return false; + } + // Create a DateTime object of the payment_date, so we can adjust as needed. + $subscription_payment_date = new \DateTime( $this->posted['payment_date'] ); + + // To make sure we don't inadvertently fail, make sure the date was parsed correctly before working with it. + if ( ! $subscription_payment_date instanceof \DateTime ) { + return false; + } + + /** + * 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. + return $subscription_payment_date->format( 'Y-m-d H:i:s' ); + } + + /** + * 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. + */ + private function debug_log( $message ) { + edd_debug_log( 'PayPal Commerce IPN: ' . $message ); + } +} diff --git a/src/Gateways/PayPal/Webhooks/Events/Customer_Dispute_Created.php b/src/Gateways/PayPal/Webhooks/Events/Customer_Dispute_Created.php new file mode 100644 index 00000000000..17c236894ec --- /dev/null +++ b/src/Gateways/PayPal/Webhooks/Events/Customer_Dispute_Created.php @@ -0,0 +1,80 @@ +event->resource_type ) || 'dispute' !== $this->event->resource_type ) { + throw new \Exception( 'Missing or invalid resource_type.', 200 ); + } + + if ( ! isset( $this->event->resource->disputed_transactions ) || ! is_array( $this->event->resource->disputed_transactions ) ) { + throw new \Exception( 'Missing or invalid disputed_transactions.', 200 ); + } + + $dispute_id = $this->event->resource->dispute_id; + $messages = $this->event->resource->messages; + $message = ! empty( $messages ) ? reset( $messages ) : false; + foreach ( $this->event->resource->disputed_transactions as $disputed_transaction ) { + if ( ! isset( $disputed_transaction->seller_transaction_id ) ) { + continue; + } + + $order_id = edd_get_order_id_from_transaction_id( $disputed_transaction->seller_transaction_id ); + if ( ! $order_id ) { + continue; + } + $order = edd_get_order( $order_id ); + if ( 'on_hold' === $order->status ) { + continue; + } + $reasons = array_unique( wp_list_pluck( $disputed_transaction->items, 'reason' ) ); + edd_record_order_dispute( $order_id, $dispute_id, $reasons ); + edd_add_note( + array( + 'object_type' => 'order', + 'object_id' => $order_id, + 'content' => sprintf( + /* translators: 1: Dispute ID, 2: Dispute reason code. Example: The PayPal transaction has been disputed. Case ID: PP-R-NMW-10060094. Reason given: non_receipt. */ + __( 'The PayPal transaction has been disputed. Case ID: %1$s. Reason given: %2$s.', 'easy-digital-downloads' ), + $dispute_id, + implode( ', ', $reasons ) + ), + ) + ); + + if ( ! empty( $message ) ) { + edd_add_note( + array( + 'object_type' => 'order', + 'object_id' => $order_id, + 'content' => sprintf( + /* translators: dispute message added by the customer */ + __( 'PayPal Dispute Message: %s', 'easy-digital-downloads' ), + $message->content + ), + ) + ); + } + } + } +} diff --git a/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Completed.php b/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Completed.php new file mode 100644 index 00000000000..634060713ec --- /dev/null +++ b/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Completed.php @@ -0,0 +1,82 @@ +get_order_from_capture(); + + // Bail if the payment has already been completed. + if ( 'complete' === $order->status ) { + edd_debug_log( 'PayPal Commerce - Exiting webhook, as order is already complete.' ); + + return; + } + + if ( empty( $this->event->resource->status ) || 'COMPLETED' !== strtoupper( $this->event->resource->status ) ) { + throw new \Exception( 'Capture status is not complete.', 200 ); + } + + if ( ! isset( $this->event->resource->amount->value ) ) { + throw new \Exception( 'Missing amount value.', 200 ); + } + + if ( ! isset( $this->event->resource->amount->currency_code ) || strtoupper( $this->event->resource->amount->currency_code ) !== strtoupper( $order->currency ) ) { + throw new \Exception( sprintf( 'Missing or invalid currency code. Expected: %s; PayPal: %s', $order->currency, esc_html( $this->event->resource->amount->currency_code ) ), 200 ); + } + + $paypal_amount = (float) $this->event->resource->amount->value; + $order_amount = edd_get_payment_amount( $order->id ); + + if ( $paypal_amount < $order_amount ) { + edd_record_gateway_error( + __( 'Webhook Error', 'easy-digital-downloads' ), + sprintf( + /* translators: %s: webhook data */ + __( 'Invalid payment amount in webhook response. Webhook data: %s', 'easy-digital-downloads' ), + json_encode( $this->event ) + ) + ); + + edd_update_order_status( $order->id, 'failed' ); + edd_add_note( + array( + 'object_type' => 'order', + 'object_id' => $order->id, + 'content' => sprintf( + /* translators: %1$s is the order amount, %2$s is the PayPal amount */ + __( 'Payment failed due to invalid amount in PayPal webhook. Expected amount: %1$s; PayPal amount: %2$s.', 'easy-digital-downloads' ), + $order_amount, + esc_html( $paypal_amount ) + ), + ) + ); + + throw new \Exception( sprintf( 'Webhook amount (%s) doesn\'t match payment amount (%s).', esc_html( $paypal_amount ), esc_html( $order_amount ) ), 200 ); + } + + edd_update_order_status( $order->id, 'complete' ); + } +} diff --git a/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Denied.php b/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Denied.php new file mode 100644 index 00000000000..3b206430416 --- /dev/null +++ b/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Denied.php @@ -0,0 +1,34 @@ +get_order_from_capture(); + + edd_update_order_status( $order->id, 'failed' ); + + edd_add_note( array( + 'object_type' => 'order', + 'object_id' => $order->id, + 'content' => __( 'PayPal transaction denied.', 'easy-digital-downloads' ), + ) ); + } +} diff --git a/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Refunded.php b/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Refunded.php new file mode 100644 index 00000000000..feb3b6dabcd --- /dev/null +++ b/src/Gateways/PayPal/Webhooks/Events/Payment_Capture_Refunded.php @@ -0,0 +1,114 @@ +refund_transaction_exists() ) { + edd_debug_log( 'PayPal Commerce - Exiting webhook, as refund transaction already exists.' ); + + return; + } + + $order = $this->get_order_from_refund(); + + if ( 'refunded' === $order->status ) { + edd_debug_log( 'PayPal Commerce - Exiting webhook, as payment status is already refunded.' ); + + return; + } + + $order_amount = edd_get_payment_amount( $order->id ); + $refunded_amount = isset( $this->event->resource->amount->value ) ? $this->event->resource->amount->value : $order_amount; + $currency = isset( $this->event->resource->amount->currency_code ) ? $this->event->resource->amount->currency_code : $order->currency; + + $payment_note = sprintf( + /* translators: 1: Amount refunded; %2$s - Original payment ID; %3$s - Refund transaction ID */ + 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( $this->event->resource->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, + ) + ); + $refund_id = edd_refund_order( $order->id ); + if ( $refund_id ) { + edd_add_order_transaction( + array( + 'object_type' => 'order', + 'object_id' => $refund_id, + 'transaction_id' => $this->event->resource->id, + 'gateway' => 'paypal_commerce', + 'total' => $refunded_amount, + 'status' => 'complete', + 'currency' => $this->event->resource->amount->currency_code, + ) + ); + } else { + edd_update_order_status( $order->id, 'refunded' ); + } + } + } + + /** + * Determines whether a transaction record exists for this refund. + * + * @since 3.0 + * + * @return bool + * @throws \Exception + */ + private function refund_transaction_exists() { + if ( ! isset( $this->event->resource->id ) ) { + throw new \Exception( 'No resource ID found.', 200 ); + } + + $transaction = edd_get_order_transaction_by( 'transaction_id', $this->event->resource->id ); + + return ! empty( $transaction ); + } +} diff --git a/src/Gateways/PayPal/Webhooks/Events/Webhook_Event.php b/src/Gateways/PayPal/Webhooks/Events/Webhook_Event.php new file mode 100644 index 00000000000..19bcb3724ca --- /dev/null +++ b/src/Gateways/PayPal/Webhooks/Events/Webhook_Event.php @@ -0,0 +1,291 @@ +request = $request; + + // `get_params()` returns an array, but we want an object. + $this->event = json_decode( json_encode( $this->request->get_params() ) ); + } + + /** + * Handles the webhook event. + * + * @throws \Exception + */ + public function handle() { + $this->process_event(); + } + + /** + * Processes the event. + * + * @since 2.11 + * @return void + */ + abstract protected function process_event(); + + /** + * Retrieves an Order record from a capture event. + * + * @since 3.0 + * + * @return Order + * @throws \Exception + */ + protected function get_order_from_capture() { + if ( 'capture' !== $this->request->get_param( 'resource_type' ) ) { + throw new \Exception( sprintf( 'get_payment_from_capture() - Invalid resource type: %s', $this->request->get_param( 'resource_type' ) ) ); + } + + if ( empty( $this->event->resource ) ) { + throw new \Exception( sprintf( 'get_payment_from_capture() - Missing event resource.' ) ); + } + + return $this->get_order_from_capture_object( $this->event->resource ); + } + + /** + * Retrieves an Order record from a capture object. + * + * @param object $resource + * + * @since 3.0 + * + * @return Order + * @throws \Exception + */ + protected function get_order_from_capture_object( $resource ) { + $order = false; + + if ( ! empty( $resource->custom_id ) && is_numeric( $resource->custom_id ) ) { + $order = edd_get_order( $resource->custom_id ); + } + + if ( empty( $order ) && ! empty( $resource->id ) ) { + $order_id = edd_get_order_id_from_transaction_id( $resource->id ); + $order = $order_id ? edd_get_order( $order_id ) : false; + } + + if ( ! $order instanceof Order ) { + throw new \Exception( 'get_order_from_capture_object() - Failed to locate order.', 200 ); + } + + /* + * Verify the transaction ID. This covers us in case we fetched the order via `custom_id`, but + * it wasn't actually an EDD-initiated payment. + */ + $order_transaction_id = $order->get_transaction_id(); + if ( $order_transaction_id !== $resource->id ) { + throw new \Exception( sprintf( + 'get_order_from_capture_object() - Transaction ID mismatch. Expected: %s; Actual: %s', + $order_transaction_id, + $resource->id + ), 200 ); + } + + return $order; + } + + /** + * Retrieves an Order record from a refund event. + * + * @since 3.0 + * + * @return Order + * @throws API_Exception + * @throws Authentication_Exception + * @throws \Exception + */ + protected function get_order_from_refund() { + edd_debug_log( sprintf( + 'PayPal Commerce Webhook - get_payment_from_capture_object() - Resource type: %s; Resource ID: %s', + $this->request->get_param( 'resource_type' ), + $this->event->resource->id + ) ); + + if ( empty( $this->event->resource->links ) || ! is_array( $this->event->resource->links ) ) { + throw new \Exception( 'Missing resources.', 200 ); + } + + $order_link = current( array_filter( $this->event->resource->links, function ( $link ) { + return ! empty( $link->rel ) && 'up' === strtolower( $link->rel ); + } ) ); + + if ( empty( $order_link->href ) ) { + throw new \Exception( 'Missing order link.', 200 ); + } + + // Based on the payment link, determine which mode we should act in. + if ( false === strpos( $order_link->href, 'sandbox.paypal.com' ) ) { + $mode = API::MODE_LIVE; + } else { + $mode = API::MODE_SANDBOX; + } + + // Look up the full order record in PayPal. + $api = new API( $mode ); + $response = $api->make_request( $order_link->href, array(), array(), $order_link->method ); + + if ( 200 !== $api->last_response_code ) { + throw new API_Exception( sprintf( 'Invalid response code when retrieving order record: %d', $api->last_response_code ) ); + } + + if ( empty( $response->id ) ) { + throw new API_Exception( 'Missing order ID from API response.' ); + } + + return $this->get_order_from_capture_object( $response ); + } + + /** + * Retrieves an EDD_Payment record from a capture event. + * + * @since 2.11 + * @deprecated 3.0 In favour of `get_order_from_capture()` + * @see Webhook_Event::get_order_from_capture() + * + * @return \EDD_Payment + * @throws \Exception + */ + protected function get_payment_from_capture() { + if ( 'capture' !== $this->request->get_param( 'resource_type' ) ) { + throw new \Exception( sprintf( 'get_payment_from_capture() - Invalid resource type: %s', $this->request->get_param( 'resource_type' ) ) ); + } + + if ( empty( $this->event->resource ) ) { + throw new \Exception( sprintf( 'get_payment_from_capture() - Missing event resource.' ) ); + } + + return $this->get_payment_from_capture_object( $this->event->resource ); + } + + /** + * Retrieves an EDD_Payment record from a capture object. + * + * @param object $resource + * + * @since 2.11 + * @deprecated 3.0 In favour of `get_order_from_capture_object()` + * @see Webhook_Event::get_order_from_capture_object + * + * @return \EDD_Payment + * @throws \Exception + */ + protected function get_payment_from_capture_object( $resource ) { + $payment = false; + + if ( ! empty( $resource->custom_id ) && is_numeric( $resource->custom_id ) ) { + $payment = edd_get_payment( $resource->custom_id ); + } + + if ( empty( $payment ) && ! empty( $resource->id ) ) { + $payment_id = edd_get_purchase_id_by_transaction_id( $resource->id ); + $payment = $payment_id ? edd_get_payment( $payment_id ) : false; + } + + if ( ! $payment instanceof \EDD_Payment ) { + throw new \Exception( 'get_payment_from_capture_object() - Failed to locate payment.', 200 ); + } + + /* + * Verify the transaction ID. This covers us in case we fetched the payment via `custom_id`, but + * it wasn't actually an EDD-initiated payment. + */ + if ( $payment->transaction_id !== $resource->id ) { + throw new \Exception( sprintf( 'get_payment_from_capture_object() - Transaction ID mismatch. Expected: %s; Actual: %s', $payment->transaction_id, $resource->id ), 200 ); + } + + return $payment; + } + + /** + * Retrieves an EDD_Payment record from a refund event. + * + * @since 2.11 + * @deprecated 3.0 In favour of `get_order_from_refund` + * @see Webhook_Event::get_order_from_refund + * + * @return \EDD_Payment + * @throws API_Exception + * @throws Authentication_Exception + * @throws \Exception + */ + protected function get_payment_from_refund() { + edd_debug_log( sprintf( 'PayPal Commerce Webhook - get_payment_from_refund() - Resource type: %s; Resource ID: %s', $this->request->get_param( 'resource_type' ), $this->event->resource->id ) ); + + if ( empty( $this->event->resource->links ) || ! is_array( $this->event->resource->links ) ) { + throw new \Exception( 'Missing resources.', 200 ); + } + + $order_link = current( array_filter( $this->event->resource->links, function ( $link ) { + return ! empty( $link->rel ) && 'up' === strtolower( $link->rel ); + } ) ); + + if ( empty( $order_link->href ) ) { + throw new \Exception( 'Missing order link.', 200 ); + } + + // Based on the payment link, determine which mode we should act in. + if ( false === strpos( $order_link->href, 'sandbox.paypal.com' ) ) { + $mode = API::MODE_LIVE; + } else { + $mode = API::MODE_SANDBOX; + } + + // Look up the full order record in PayPal. + $api = new API( $mode ); + $response = $api->make_request( $order_link->href, array(), array(), $order_link->method ); + + if ( 200 !== $api->last_response_code ) { + throw new API_Exception( sprintf( 'Invalid response code when retrieving order record: %d', $api->last_response_code ) ); + } + + if ( empty( $response->id ) ) { + throw new API_Exception( 'Missing order ID from API response.' ); + } + + return $this->get_payment_from_capture_object( $response ); + } + +} diff --git a/src/Gateways/PayPal/Webhooks/Webhook_Handler.php b/src/Gateways/PayPal/Webhooks/Webhook_Handler.php new file mode 100644 index 00000000000..6b3ec69346e --- /dev/null +++ b/src/Gateways/PayPal/Webhooks/Webhook_Handler.php @@ -0,0 +1,177 @@ + \WP_REST_Server::READABLE, + 'callback' => array( $this, 'handle_test' ), + 'permission_callback' => '__return_true', + ) ); + + register_rest_route( self::REST_NAMESPACE, self::REST_ROUTE, array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'handle_request' ), + 'permission_callback' => array( $this, 'validate_request' ) + ) ); + } + + /** + * Handles the current request. + * + * @param \WP_REST_Request $request + * + * @since 2.11 + * @return \WP_REST_Response + */ + public function handle_request( \WP_REST_Request $request ) { + edd_debug_log( sprintf( + 'PayPal Commerce webhook endpoint loaded. Mode: %s; Event: %s', + ( edd_is_test_mode() ? 'sandbox' : 'live' ), + $request->get_param( 'event_type' ) + ) ); + + edd_debug_log( sprintf( 'Payload: %s', json_encode( $this->event ) ) ); // @todo remove + + try { + // We need to match this event to one of our handlers. + $events = get_webhook_events(); + if ( ! array_key_exists( $request->get_param( 'event_type' ), $events ) ) { + throw new \Exception( sprintf( 'Event not registered. Event: %s', esc_html( $request->get_param( 'event_type' ) ) ), 200 ); + } + + $class_name = $events[ $request->get_param( 'event_type' ) ]; + + if ( ! class_exists( $class_name ) ) { + throw new \Exception( sprintf( 'Class %s doesn\'t exist for event type.', $class_name ), 500 ); + } + + /** + * Initialize the handler for this event. + * + * @var PayPal\Webhooks\Events\Webhook_Event $handler + */ + $handler = new $class_name( $request ); + + if ( ! method_exists( $handler, 'handle' ) ) { + throw new \Exception( sprintf( 'handle() method doesn\'t exist in class %s.', $class_name ), 500 ); + } + + edd_debug_log( sprintf( 'PayPal Commerce Webhook - Passing to handler %s', esc_html( $class_name ) ) ); + + $handler->handle(); + + $action_key = sanitize_key( strtolower( str_replace( '.', '_', $request->get_param( 'event_type' ) ) ) ); + /** + * Triggers once the handler has run successfully. + * $action_key is a formatted version of the event type: + * - All lowercase + * - Full stops `.` replaced with underscores `_` + * + * Note: This action hook exists so you can execute custom code *after* a handler has run. + * If you're registering a custom event, please build a custom handler by extending + * the `Webhook_Event` class and not via this hook. + * + * @param \WP_REST_Request $event + * + * @since 2.11 + */ + do_action( 'edd_paypal_webhook_event_' . $action_key, $request ); + + return new \WP_REST_Response( 'Success', 200 ); + } catch ( PayPal\Exceptions\Authentication_Exception $e ) { + // Failure with PayPal credentials. + edd_debug_log( sprintf( 'PayPal Commerce Webhook - Exiting due to authentication exception. Message: %s', $e->getMessage() ), true ); + + return new \WP_REST_Response( $e->getMessage(), 403 ); + } catch ( PayPal\Exceptions\API_Exception $e ) { + // Failure with a PayPal API request. + edd_debug_log( sprintf( 'PayPal Commerce Webhook - Failure due to an API exception. Message: %s', $e->getMessage() ) ); + + return new \WP_REST_Response( $e->getMessage(), 500 ); + } catch ( \Exception $e ) { + edd_debug_log( sprintf( 'PayPal Commerce - Exiting webhook due to an exception. Message: %s', $e->getMessage() ), true ); + + $response_code = $e->getCode() > 0 ? $e->getCode() : 500; + + return new \WP_REST_Response( $e->getMessage(), $response_code ); + } + } + + /** + * Validates the webhook + * + * @since 2.11 + * @return bool|\WP_Error + */ + public function validate_request() { + if ( ! PayPal\has_rest_api_connection() ) { + return new \WP_Error( 'missing_api_credentials', 'API credentials not set.' ); + } + + $this->event = json_decode( file_get_contents( 'php://input' ) ); + + try { + Webhook_Validator::validate_from_request( $this->event ); + + edd_debug_log( 'PayPal Commerce webhook successfully validated.' ); + + return true; + } catch ( \Exception $e ) { + return new \WP_Error( 'validation_failure', $e->getMessage() ); + } + } + + /** + * Handles the webhook test request. + * + * @since 3.2.0 + * + * @param \WP_REST_Request $request + */ + public function handle_test( \WP_REST_Request $request ) { + edd_debug_log( 'PayPal Commerce webhook test endpoint loaded.' ); + + return new \WP_REST_Response( array( 'message' => 'success' ), 200 ); + } +} diff --git a/src/Gateways/PayPal/Webhooks/Webhook_Validator.php b/src/Gateways/PayPal/Webhooks/Webhook_Validator.php new file mode 100644 index 00000000000..af958461426 --- /dev/null +++ b/src/Gateways/PayPal/Webhooks/Webhook_Validator.php @@ -0,0 +1,168 @@ + 'auth_algo', + 'PAYPAL-CERT-URL' => 'cert_url', + 'PAYPAL-TRANSMISSION-ID' => 'transmission_id', + 'PAYPAL-TRANSMISSION-SIG' => 'transmission_sig', + 'PAYPAL-TRANSMISSION-TIME' => 'transmission_time' + ); + + /** + * Webhook_Validator constructor. + * + * @param array $headers + * @param object $event + * + * @since 2.11 + */ + public function __construct( $headers, $event ) { + $this->headers = array_change_key_case( $headers, CASE_UPPER ); + $this->event = $event; + } + + /** + * Verifies the signature. + * + * @since 2.11 + * @return true + * @throws API_Exception + * @throws \InvalidArgumentException + */ + public function verify_signature() { + $api = new API(); + + $response = $api->make_request( 'v1/notifications/verify-webhook-signature', $this->get_body() ); + + if ( 200 !== $api->last_response_code ) { + throw new API_Exception( sprintf( + 'Invalid response code: %d. Response: %s', + $api->last_response_code, + json_encode( $response ) + ) ); + } + + if ( empty( $response->verification_status ) || 'SUCCESS' !== strtoupper( $response->verification_status ) ) { + throw new API_Exception( sprintf( + 'Verification failure. Response: %s', + json_encode( $response ) + ) ); + } + + return true; + } + + /** + * Validates that we have all the required headers. + * + * @since 2.11 + * @throws \InvalidArgumentException + */ + private function validate_headers() { + foreach ( array_keys( $this->header_map ) as $required_key ) { + if ( ! array_key_exists( $required_key, $this->headers ) ) { + throw new \InvalidArgumentException( sprintf( + 'Missing PayPal header %s', + $required_key + ) ); + } + } + } + + /** + * Retrieves the webhook ID for the current mode. + * + * @since 2.11 + * @return string + * @throws \Exception + */ + private function get_webhook_id() { + $id = get_webhook_id(); + + if ( empty( $id ) ) { + throw new \Exception( 'No webhook created in current mode.' ); + } + + return $id; + } + + /** + * Builds arguments for the body of the API request. + * + * @return array + * @throws \InvalidArgumentException + * @throws \Exception + */ + private function get_body() { + $this->validate_headers(); + + $body = array( + 'webhook_id' => $this->get_webhook_id(), + 'webhook_event' => $this->event + ); + + // Add arguments from the headers. + foreach ( $this->header_map as $header_key => $body_key ) { + $body[ $body_key ] = $this->headers[ $header_key ]; + } + + return $body; + } + + /** + * Validates the webhook from the current request. + * + * @param object $event Webhook event. + * + * @since 2.11 + * @return true + * @throws API_Exception + * @throws \InvalidArgumentException + */ + public static function validate_from_request( $event ) { + $validator = new Webhook_Validator( getallheaders(), $event ); + + return $validator->verify_signature(); + } + +} diff --git a/src/Gateways/Stripe/Admin/Connect.php b/src/Gateways/Stripe/Admin/Connect.php new file mode 100644 index 00000000000..bb30eed11a3 --- /dev/null +++ b/src/Gateways/Stripe/Admin/Connect.php @@ -0,0 +1,394 @@ +', + '' + ); + } + } + + // Webhooks not found. + if ( false === $key ) { + if ( ! self::is_listener_ssl() ) { + return sprintf( + /* translators: 1: Webhooks setup link, 2: Closing anchor tag */ + __( 'Webhooks cannot be automatically set up because your site is not using HTTPS. %1$sManually add webhooks%2$s.', 'easy-digital-downloads' ), + '', + '' + ); + } + + return sprintf( + /* translators: 1: Webhooks setup link, 2: Closing anchor tag, 3: Manual setup link */ + __( 'Webhooks not found. %1$sAutomatically set up webhooks%2$s or %3$sadd them to your account manually%2$s.', 'easy-digital-downloads' ), + '', + '', + '' + ); + } + + return __( 'Webhooks are configured correctly.', 'easy-digital-downloads' ); + } + + /** + * Set up webhooks. + * + * @since 3.3.4 + * @param array $data Data. + */ + public static function create_webhooks( $data ) { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + if ( empty( $data['_wpnonce'] ) || ! wp_verify_nonce( $data['_wpnonce'], 'edd-create-stripe-webhooks' ) ) { + return; + } + + $message = 'stripe_webhooks_error'; + // Only create webhooks in production mode if the listener is SSL. + if ( ! edd_is_test_mode() && ! self::is_listener_ssl() ) { + self::redirect( 'stripe_webhooks_error_ssl' ); + } + + try { + $webhooks = edds_api_request( + 'WebhookEndpoint', + 'create', + array( + 'url' => self::get_listener_url(), + 'enabled_events' => self::get_event_endpoints(), + 'api_version' => EDD_STRIPE_API_VERSION, + ) + ); + + $message = 'stripe_webhooks_created'; + } catch ( \EDD\Vendor\Stripe\ApiErrorException $e ) { + // Do nothing. + } + + self::redirect( $message ); + } + + /** + * Get the webhooks setup link to manually set up webhooks. + * + * @since 3.3.4 + * @return string + */ + private static function get_workbench_url() { + return add_query_arg( + array( + 'events' => urlencode( implode( ',', self::get_event_endpoints() ) ), + ), + 'https://dashboard.stripe.com/webhooks/create' + ); + } + + /** + * Get the list of event endpoints. + * + * @since 3.3.4 + * @return array + */ + private static function get_event_endpoints() { + /** + * Filter the list of Stripe webhook endpoints. + * + * @since 3.3.4 + * @param array $endpoints The list of endpoints. + */ + return apply_filters( + 'edd_stripe_webhook_endpoints', + array( + 'charge.refunded', + 'charge.succeeded', + 'charge.dispute.created', + 'customer.subscription.created', + 'customer.subscription.deleted', + 'customer.subscription.updated', + 'invoice.payment_failed', + 'invoice.payment_succeeded', + 'mandate.updated', + 'payment_intent.amount_capturable_updated', + 'payment_intent.canceled', + 'payment_intent.created', + 'payment_intent.partially_funded', + 'payment_intent.payment_failed', + 'payment_intent.processing', + 'payment_intent.requires_action', + 'payment_intent.succeeded', + 'payment_method.attached', + 'payment_method.automatically_updated', + 'payment_method.detached', + 'payment_method.updated', + 'radar.early_fraud_warning.created', + 'review.closed', + 'review.opened', + 'setup_intent.canceled', + 'setup_intent.created', + 'setup_intent.requires_action', + 'setup_intent.setup_failed', + 'setup_intent.succeeded', + ) + ); + } + + /** + * Gets the listener URL. + * + * @since 3.3.4 + * @param bool $include_index Whether to include the index.php file. + * @param bool $include_trailing_slash Whether to include the trailing slash. + * @return string + */ + private static function get_listener_url( $include_index = true, $include_trailing_slash = true ) { + $home_url = $include_index + ? home_url( 'index.php' ) + : home_url(); + + if ( ! $include_index && $include_trailing_slash ) { + $home_url = trailingslashit( $home_url ); + } + + return add_query_arg( + array( + 'edd-listener' => 'stripe', + ), + $home_url + ); + } + + /** + * Get the webhooks setup link. + * + * @since 3.3.4 + * @return string + */ + private static function get_webhooks_setup_link() { + return wp_nonce_url( + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + 'section' => 'edd-stripe', + 'edd-action' => 'create_stripe_webhooks', + ) + ), + 'edd-create-stripe-webhooks' + ); + } + + /** + * Check if the listener is SSL. + * + * @since 3.3.5 + * @return bool + */ + private static function is_listener_ssl() { + return 'https' === wp_parse_url( self::get_listener_url(), PHP_URL_SCHEME ); + } + + /** + * Redirect to the Stripe settings page. + * + * @since 3.3.5 + * @param string $message Message. + */ + private static function redirect( string $message ) { + edd_redirect( + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + 'section' => 'edd-stripe', + 'edd-message' => $message, + ) + ) + ); + } + + /** + * Get the list of webhook endpoints. + * + * @since 3.3.5 + * @return array|string + */ + private static function get_endpoints() { + try { + $webhooks = edds_api_request( + 'WebhookEndpoint', + 'all', + array( + 'limit' => 100, + ) + ); + } catch ( \Exception $e ) { + return $e->getMessage(); + } + + return $webhooks['data']; + } + + /** + * Check if the webhook endpoint exists. + * + * @since 3.3.5 + * @param array $endpoints Endpoints. + * @return int|bool + */ + private static function check_endpoints( $endpoints ) { + $urls = wp_list_pluck( $endpoints, 'url' ); + + // Ensure we're only working with 'enabled' webhook endpoints. + foreach ( $urls as $key => $url ) { + if ( 'enabled' !== $endpoints[ $key ]->status ) { + unset( $urls[ $key ] ); + } + } + + $all_listeners = array( + self::get_listener_url(), // /index.php?edd-listener=stripe + self::get_listener_url( false, false ), // ?edd-listener=stripe + self::get_listener_url( false, true ), // /?edd-listener=stripe + ); + + foreach ( $all_listeners as $listener ) { + $key = array_search( $listener, $urls, true ); + + if ( false !== $key ) { + $webhook_id = $endpoints[ $key ]->id; + break; + } + } + + if ( false === $key ) { + return false; + } + + $update_data = array(); + + // Ensure that even if we did find a webhook that the URL is correct. + if ( self::get_listener_url() !== $urls[ $key ] ) { + $update_data['url'] = self::get_listener_url(); + } + + // Check to see if we have any of our required events missing. + $missing_events_count = 0; + foreach ( self::get_event_endpoints() as $event ) { + if ( ! in_array( $event, $endpoints[ $key ]->enabled_events, true ) ) { + ++$missing_events_count; + } + } + + if ( $missing_events_count > 0 ) { + $update_data['enabled_events'] = self::get_event_endpoints(); + } + + // If we have data to update, do so. + if ( ! empty( $update_data ) ) { + self::update_webhook( $webhook_id, $update_data ); + } + + // Now lets ensure we never have any duplicates and disable the ones we find. + self::disable_duplicate_webhooks( $webhook_id ); + + return $key; + } + + /** + * Update a webhook. + * + * @since 3.3.5 + * @param string $webhook_id Webhook ID. + * @param array $data Data. + * @return bool + */ + private static function update_webhook( $webhook_id, $data ) { + try { + edds_api_request( + 'WebhookEndpoint', + 'update', + $webhook_id, + $data + ); + } catch ( \EDD\Vendor\Stripe\ApiErrorException $e ) { + return false; + } + + return true; + } + + /** + * Disable duplicate webhooks. + * + * It is possible that we end up with multiple webhooks due to people adding them manually, so we'll + * ensure that we only have one webhook that matches our listener URL. Searching for both the + * listener URL with and without index.php, keeping the one that matches our $valid_webhook_id. + * + * @since 3.3.5 + * @param string $valid_webhook_id Webhook ID that we want to keep. + */ + private static function disable_duplicate_webhooks( $valid_webhook_id ) { + $endpoints = self::get_endpoints(); + + $listener_urls = array( + self::get_listener_url(), // /index.php?edd-listener=stripe + self::get_listener_url( false, false ), // ?edd-listener=stripe + self::get_listener_url( false, true ), // /?edd-listener=stripe + ); + + foreach ( $endpoints as $endpoint ) { + // Skip the webhook that we want to keep. + if ( $endpoint->id === $valid_webhook_id ) { + continue; + } + + if ( in_array( $endpoint->url, $listener_urls, true ) ) { + self::update_webhook( + $endpoint->id, + array( 'disabled' => true ) + ); + } + } + } +} diff --git a/src/Gateways/Stripe/Admin/LicenseManager.php b/src/Gateways/Stripe/Admin/LicenseManager.php new file mode 100644 index 00000000000..904b88d0e17 --- /dev/null +++ b/src/Gateways/Stripe/Admin/LicenseManager.php @@ -0,0 +1,467 @@ +are_requirements_met() ) { + return; + } + $notifications_to_dismiss = array(); + $notifications_to_reset = array(); + $license = $this->get_license(); + if ( $license->is_license_valid() ) { + $notifications_to_dismiss[] = 'edds-missing'; + } else { + $notifications_to_reset[] = 'edds-missing'; + } + if ( $license->is_expired() ) { + if ( $license->is_in_grace_period() ) { + $notifications_to_dismiss[] = 'edds-expired'; + $notifications_to_reset[] = 'edds-grace'; + } else { + $notifications_to_reset[] = 'edds-expired'; + $notifications_to_dismiss[] = 'edds-grace'; + } + } + if ( $license->is_expiring_soon() ) { + $notifications_to_reset[] = 'edds-expiring'; + } else { + $notifications_to_dismiss[] = 'edds-expiring'; + } + if ( ! empty( $notifications_to_reset ) ) { + $this->update_notifications( $notifications_to_reset, 0 ); + } + if ( ! empty( $notifications_to_dismiss ) ) { + $this->update_notifications( $notifications_to_dismiss, 1 ); + } + } + + /** + * When the license is updated, refresh the license object. and run the license check. + * + * @since 3.2.0 + * @param \EDD\Licensing\License $license The license object. + * @return void + */ + public function license_updated( $license ) { + if ( ! $this->should_check_license( $license ) ) { + return; + } + $this->get_license( true ); + $this->check_license(); + } + + /** + * Registers admin notices for critical license issues, which show in addition to the notification. + * + * @since 3.2.0 + * @return void + */ + public function register_admin_notices() { + if ( edd_is_pro() ) { + return; + } + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + if ( function_exists( 'edd_is_admin_page' ) && ! edd_is_admin_page() ) { + return; + } + if ( ! $this->should_show_warnings() ) { + return; + } + + $license = $this->get_license(); + $admin_notice_args = array( + 'id' => 'missing', + 'type' => 'error', + ); + if ( $license->is_in_grace_period() ) { + $admin_notice_args['id'] = 'grace'; + $admin_notice_args['type'] = 'warning'; + } elseif ( $license->is_expired() ) { + $admin_notice_args['id'] = 'expired'; + } + + $this->do_admin_notice( $admin_notice_args ); + } + + /** + * Outputs a notice on the dashboard if the license is not active. + * + * @since 3.2.0 + * @return void + */ + public function do_dashboard_notice() { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + if ( ! $this->should_show_warnings() ) { + return; + } + + $license = $this->get_license(); + $message = sprintf( + /* translators: %1$s is the opening link tag; %2$s is the closing link tag. */ + __( 'Your license is not active. Please %1$sactivate your license%2$s.', 'easy-digital-downloads' ), + '', + '' + ); + if ( $license->is_in_grace_period() || $license->is_expired() ) { + $message = sprintf( + /* translators: %1$s is the opening link tag; %2$s is the closing link tag. */ + __( 'Your license has expired. Please %1$srenew your license%2$s.', 'easy-digital-downloads' ), + '', + '' + ); + } + + ?> +
    +

    +
    + are_requirements_met() ) { + return false; + } + if ( ! empty( $license->item_id ) && 167 === (int) $license->item_id ) { + return true; + } + $pass_manager = new \EDD\Admin\Pass_Manager(); + if ( $pass_manager::pass_compare( $pass_manager->highest_pass_id, $pass_manager::EXTENDED_PASS_ID, '>=' ) ) { + return true; + } + if ( $pass_manager->hasExtendedPass() || $pass_manager->hasAllAccessPass() ) { + return true; + } + + return false; + } + + /** + * Gets the license object. + * + * @param bool $force Whether to force a new license object. + * @return \EDD\Gateways\Stripe\License + */ + private function get_license( $force = false ) { + if ( is_null( $this->license ) || $force ) { + $this->license = new \EDD\Gateways\Stripe\License(); + } + + return $this->license; + } + + /** + * Updates the dismissed status of the given notifications. + * If the notification does not exist, it is ignored. + * + * @since 3.2.0 + * @param array $notifications The notifications to update. + * @param int $dismissed Whether the notifications are dismissed. + */ + private function update_notifications( array $notifications, int $dismissed ) { + if ( empty( $notifications ) ) { + return; + } + $dismissed = (int) (bool) $dismissed; + $notifications_db = new \EDD\Database\NotificationsDB(); + foreach ( $notifications as $remote_id ) { + $notification = $notifications_db->get_item_by( 'remote_id', $remote_id ); + if ( $notification ) { + if ( (int) $notification->dismissed !== $dismissed ) { + if ( $dismissed ) { + $notifications_db->update( $notification->id, array( 'dismissed' => $dismissed ) ); + } else { + $notifications_db->maybe_add_local_notification( + $this->get_notification_by_remote_id( $remote_id ) + ); + } + } + } elseif ( empty( $dismissed ) ) { + $notifications_db->maybe_add_local_notification( + $this->get_notification_by_remote_id( $remote_id ) + ); + } + } + } + + /** + * Gets the notification by remote ID. + * + * @since 3.2.0 + * @param string $remote_id The remote ID of the notification. + * @return array + */ + private function get_notification_by_remote_id( $remote_id ) { + $license = $this->get_license(); + $license_name = $license->is_pass_license ? __( 'Easy Digital Downloads (Pro)', 'easy-digital-downloads' ) : __( 'Easy Digital Downloads - Stripe Pro Payment Gateway', 'easy-digital-downloads' ); + $notifications = array( + 'edds-missing' => array( + 'remote_id' => 'edds-missing', + 'title' => __( 'Easy Digital Downloads - Stripe Pro Payment Gateway Is Not Fully Activated!', 'easy-digital-downloads' ), + 'content' => __( 'Activate your license key to receive important security and feature updates and remove application fees.', 'easy-digital-downloads' ), + 'buttons' => array( + array( + 'type' => 'primary', + 'url' => $license->get_licensing_url(), + 'text' => __( 'Complete Activation', 'easy-digital-downloads' ), + ), + array( + 'type' => 'secondary', + 'url' => 'https://easydigitaldownloads.com/downloads/stripe-gateway/', + 'text' => __( 'Learn More', 'easy-digital-downloads' ), + ), + ), + 'type' => 'warning', + ), + 'edds-grace' => array( + 'remote_id' => 'edds-grace', + 'title' => sprintf( + /* translators: %s: name of the license. */ + __( 'Your %s license has expired!', 'easy-digital-downloads' ), + $license_name + ), + 'content' => sprintf( + /* translators: %s: date the grace period ends. */ + __( 'Renew your license before %s to continue using Stripe without paying additional fees and to continue receiving important security and feature updates.', 'easy-digital-downloads' ), + $license->get_grace_period_end_date() + ), + 'buttons' => array( + array( + 'type' => 'primary', + 'url' => $license->get_renewal_url( 'expiring' ), + 'text' => __( 'Renew License', 'easy-digital-downloads' ), + ), + array( + 'type' => 'secondary', + 'url' => 'https://easydigitaldownloads.com/downloads/stripe-gateway/', + 'text' => __( 'Learn More', 'easy-digital-downloads' ), + ), + ), + 'type' => 'warning', + ), + 'edds-expired' => array( + 'remote_id' => 'edds-expired', + 'title' => sprintf( + /* translators: %s: name of the license. */ + __( 'Your %s license has expired!', 'easy-digital-downloads' ), + $license_name + ), + 'content' => __( 'You are now paying additional fees with every Stripe transaction. You are no longer receiving important security and feature updates for Stripe Pro.', 'easy-digital-downloads' ), + 'buttons' => array( + array( + 'type' => 'primary', + 'url' => $license->get_renewal_url( 'expired' ), + 'text' => __( 'Renew License', 'easy-digital-downloads' ), + ), + array( + 'type' => 'secondary', + 'url' => '', + 'text' => __( 'Learn More', 'easy-digital-downloads' ), + ), + ), + 'type' => 'warning', + ), + 'edds-expiring' => array( + 'remote_id' => 'edds-expiring', + 'title' => sprintf( + /* translators: %s: name of the license. */ + __( 'Your %s License Is Expiring Soon!', 'easy-digital-downloads' ), + $license_name + ), + 'content' => sprintf( + /* translators: 1: the name of the license, 2: the date the license expires. */ + __( 'Your %1$s license is set to expire on %2$s. An active license key is required to create and edit payment forms, enable automatic updates, and to keep Easy Digital Downloads - Stripe Pro Payment Gateway fully activated.', 'easy-digital-downloads' ), + $license_name, + $license->get_expiration_date() + ), + 'buttons' => array( + array( + 'type' => 'primary', + 'url' => $license->get_renewal_url( 'expiring' ), + 'text' => __( 'Renew License', 'easy-digital-downloads' ), + ), + array( + 'type' => 'secondary', + 'url' => 'https://easydigitaldownloads.com/downloads/stripe-gateway/', + 'text' => __( 'Learn More', 'easy-digital-downloads' ), + ), + ), + 'type' => 'warning', + ), + ); + + return $notifications[ $remote_id ]; + } + + /** + * Outputs an admin notice. + * + * @since 3.2.0 + * @param array $args The arguments. + * @return void + */ + private function do_admin_notice( $args ) { + $args = wp_parse_args( + $args, + array( + 'id' => '', + 'is_dismissible' => true, + 'type' => 'info', + ) + ); + if ( empty( $args['id'] ) ) { + return; + } + $registry = edds_get_registry( 'admin-notices' ); + $registry->add( + $args['id'], + array( + 'type' => $args['type'], + 'dismissible' => $args['is_dismissible'], + 'message' => $this->get_message( $args['id'] ), + ) + ); + wp_enqueue_script( 'edds-admin-notices' ); + $notices = new \EDD_Stripe_Admin_Notices( $registry ); + $notices->output( $args['id'] ); + } + + /** + * Gets the message for an admin notice. + * + * @since 3.2.0 + * @param string $id The notification ID. + * @return string + */ + private function get_message( $id ) { + $notification = $this->get_notification_by_remote_id( "edds-{$id}" ); + $message = array( + '' . $notification['title'] . '', + $notification['content'], + ); + if ( ! empty( $notification['buttons'] ) ) { + $button = reset( $notification['buttons'] ); + $message[] = sprintf( + '%s', + $button['url'], + $button['type'], + $button['text'] + ); + } + $message = array_map( 'wpautop', $message ); + + return implode( '', $message ); + } + + /** + * Whether the requirements are met to show the admin notices. + * + * @since 3.2.0 + * @return bool + */ + private function are_requirements_met() { + + // Not connected (always false). + if ( empty( edd_stripe()->connect()->get_connect_id() ) ) { + return false; + } + + // Not in country that supports the fees (always false). + if ( true !== edds_stripe_connect_account_country_supports_application_fees() ) { + return false; + } + + return edd_is_gateway_active( 'stripe' ); + } + + /** + * Whether the admin warnings should be shown. + * + * @since 3.2.1 + * @return bool + */ + private function should_show_warnings() { + + // If Stripe is not connected and active, don't show the warnings. + if ( ! $this->are_requirements_met() ) { + return false; + } + + $license = $this->get_license(); + + // If the requirements are met to remove the application fee, don't show the warnings. + if ( ! edd_stripe()->application_fee->has_application_fee() ) { + // (Unless the license is in a grace period). + return $license->is_in_grace_period(); + } + + // There is an application fee, but Stripe Pro is active, so show the warnings. + if ( edds_is_pro() ) { + return true; + } + + // There isn't a Stripe qualified license, but it's EDD Lite. + if ( empty( $license->license_data->key ) && ! edd_is_pro() ) { + return false; + } + + // There is pass license active, but it's not for a pass that includes Stripe. + if ( ! empty( $license->pass_id ) ) { + return false; + } + + return true; + } +} diff --git a/src/Gateways/Stripe/Admin/Settings.php b/src/Gateways/Stripe/Admin/Settings.php new file mode 100644 index 00000000000..c1f01359309 --- /dev/null +++ b/src/Gateways/Stripe/Admin/Settings.php @@ -0,0 +1,619 @@ + array( + 'id' => 'stripe_connect_button', + 'name' => __( 'Connection Status', 'easy-digital-downloads' ), + 'desc' => edds_stripe_connect_setting_field(), + 'type' => 'descriptive_text', + 'class' => 'edd-stripe-connect-row', + ), + 'test_publishable_key' => array( + 'id' => 'test_publishable_key', + 'name' => __( 'Test Publishable Key', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your test publishable key, found in your Stripe Account Settings', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + 'class' => 'edd-hidden edds-api-key-row', + ), + 'test_secret_key' => array( + 'id' => 'test_secret_key', + 'name' => __( 'Test Secret Key', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your test secret key, found in your Stripe Account Settings', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + 'class' => 'edd-hidden edds-api-key-row', + ), + 'live_publishable_key' => array( + 'id' => 'live_publishable_key', + 'name' => __( 'Live Publishable Key', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your live publishable key, found in your Stripe Account Settings', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + 'class' => 'edd-hidden edds-api-key-row', + ), + 'live_secret_key' => array( + 'id' => 'live_secret_key', + 'name' => __( 'Live Secret Key', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your live secret key, found in your Stripe Account Settings', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + 'class' => 'edd-hidden edds-api-key-row', + ), + 'stripe_billing_fields' => array( + 'id' => 'stripe_billing_fields', + 'name' => __( 'Billing Address Display', 'easy-digital-downloads' ), + 'desc' => __( '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.

    ', 'easy-digital-downloads' ), + 'type' => 'select', + 'std' => 'full', + 'class' => $this->get_connected_class(), + 'options' => array( + 'full' => __( 'Full address', 'easy-digital-downloads' ), + 'zip_country' => __( 'Zip / Postal Code and Country only', 'easy-digital-downloads' ), + 'none' => __( 'No address fields', 'easy-digital-downloads' ), + ), + ), + 'statement_descriptor' => array( + 'id' => 'stripe_statement_descriptor', + 'name' => __( 'Statement Descriptor', 'easy-digital-downloads' ), + 'desc' => sprintf( + /* translators: 1: opening link tag (do not translate), 2: closing link tag (do not translate) */ + __( 'You can change the description of charges on a customer\'s bank statement in your %1$sStripe Settings%2$s.', 'easy-digital-downloads' ), + '', + '' + ), + 'type' => 'text', + 'faux' => true, + 'disabled' => true, + 'class' => $this->get_connected_class(), + 'field_class' => 'edd-text-loading', + ), + 'include_purchase_summary' => array( + 'id' => 'stripe_include_purchase_summary_in_statement_descriptor', + 'name' => __( 'Include Purchase Summary', 'easy-digital-downloads' ), + 'check' => __( 'Include the product name(s) purchased in the payment descriptor for card payments. If the product name(s) are too long they will be shortened automatically.', 'easy-digital-downloads' ), + 'desc' => __( 'Note: This setting does not affect non-card payment methods. Non-card payment methods will always use the Statement Descriptor above.', 'easy-digital-downloads' ), + 'type' => 'checkbox_toggle', + 'class' => $this->get_connected_class(), + ), + 'statement_descriptor_prefix' => array( + 'id' => 'stripe_statement_descriptor_prefix', + 'name' => __( 'Shortened Descriptor', 'easy-digital-downloads' ), + 'desc' => __( 'When including the purchase summary in the payment descriptor for card payments, Stripe will use this shortened description as a prefix to the purchase summary.', 'easy-digital-downloads' ), + 'type' => 'text', + 'faux' => true, + 'disabled' => true, + 'class' => edd_stripe()->connect->is_connected && ! empty( edd_get_option( 'stripe_include_purchase_summary_in_statement_descriptor', false ) ) ? 'statement-descriptor-prefix' : 'edd-hidden statement-descriptor-prefix', + 'field_class' => 'edd-text-loading', + ), + 'stripe_more_settings_header' => array( + 'id' => 'stripe_additional_settings_header', + 'name' => __( 'Additional Settings', 'easy-digital-downloads' ), + 'type' => 'header', + ), + 'stripe_restrict_assets' => array( + 'id' => 'stripe_restrict_assets', + 'name' => ( __( 'Restrict Stripe Assets', 'easy-digital-downloads' ) ), + 'check' => ( __( 'Only load Stripe.com hosted assets on pages that specifically utilize Stripe functionality.', 'easy-digital-downloads' ) ), + 'type' => 'checkbox_toggle', + 'desc' => sprintf( + /* translators: 1: opening link tag, 2: closing link tag */ + __( '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', 'easy-digital-downloads' ), + '', + '' + ), + ), + 'stripe_payment_elements_layout' => $this->get_layout_setting(), + ); + + $payment_methods = $this->get_payment_methods_setting(); + if ( $payment_methods ) { + $stripe_settings['stripe_payment_methods'] = $payment_methods; + } + + if ( _edds_legacy_elements_enabled() ) { + if ( ! edds_stripe_connect_can_manage_keys() ) { + $stripe_settings['stripe_elements_mode'] = array( + 'id' => 'stripe_elements_mode', + 'name' => __( 'Elements Mode', 'easy-digital-downloads' ), + 'desc' => __( 'Toggle between using the legacy Card Elements Stripe integration and the new Payment Elements experience.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => array( + 'card-elements' => __( 'Card Element', 'easy-digital-downloads' ), + 'payment-elements' => __( 'Payment Element', 'easy-digital-downloads' ), + ), + 'class' => 'stripe-elements-mode', + 'tooltip_title' => __( 'Transitioning to Payment Elements', 'easy-digital-downloads' ), + 'tooltip_desc' => __( '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!', 'easy-digital-downloads' ), + ); + } + + $stripe_settings['stripe_allow_prepaid'] = array( + 'id' => 'stripe_allow_prepaid', + 'name' => __( 'Prepaid Cards', 'easy-digital-downloads' ), + 'desc' => __( 'Allow prepaid cards as valid payment method.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + 'class' => $this->is_payment_elements_mode() ? 'edd-hidden card-elements-feature' : 'card-elements-feature', + ); + + $radar_rules_url = sprintf( + 'https://dashboard.stripe.com%s/settings/radar/rules', + edd_is_test_mode() ? '/test' : '' + ); + + $stripe_settings['stripe_allow_prepaid_elements_note'] = array( + 'id' => 'stripe_allow_prepaid_elements_note', + 'name' => __( 'Prepaid Cards', 'easy-digital-downloads' ), + 'desc' => sprintf( + /* translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. */ + __( 'Prepaid card allowance can now be managed in your %1$sStripe Radar Rules%2$s.', 'easy-digital-downloads' ), + '', + '' + ), + 'type' => 'descriptive_text', + 'class' => $this->is_payment_elements_mode() ? 'payment-elements-feature' : 'edd-hidden payment-elements-feature', + ); + + $stripe_settings['stripe_split_payment_fields'] = array( + 'id' => 'stripe_split_payment_fields', + 'name' => __( 'Split Credit Card Form', 'easy-digital-downloads' ), + 'desc' => __( 'Use separate card number, expiration, and CVC fields in payment forms.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + 'class' => $this->is_payment_elements_mode() ? 'edd-hidden card-elements-feature' : 'card-elements-feature', + ); + + $stripe_settings['stripe_use_existing_cards'] = array( + 'id' => 'stripe_use_existing_cards', + 'name' => __( 'Show Previously Used Cards', 'easy-digital-downloads' ), + 'desc' => __( 'Provides logged in customers with a list of previously used payment methods for faster checkout.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + 'class' => $this->is_payment_elements_mode() ? 'edd-hidden card-elements-feature' : 'card-elements-feature', + ); + + $stripe_settings['stripe_use_existing_cards_elements_note'] = array( + 'id' => 'stripe_use_existing_cards_elements_note', + 'name' => __( 'Show Previously Used Cards', 'easy-digital-downloads' ), + 'desc' => sprintf( + /* translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. */ + __( 'Previously used cards are now managed by %1$sLink by Stripe%2$s, for even better conversions and security.', 'easy-digital-downloads' ), + '', + '' + ), + 'type' => 'descriptive_text', + 'class' => $this->is_payment_elements_mode() ? 'payment-elements-feature' : 'edd-hidden payment-elements-feature', + ); + } + + $debug_setting = $this->get_debug_mode_setting(); + if ( $debug_setting ) { + $stripe_settings = array_merge( $stripe_settings, $debug_setting ); + } + + return $stripe_settings; + } + + /** + * Inserts the Test Mode toggle notice after the Test Mode checkbox. + * + * @since 3.3.5 + * @param array $settings The settings array. + * @return array The modified settings array. + */ + public function insert_toggle_notice( $settings ) { + if ( ! $this->is_gateway_settings_screen() ) { + return $settings; + } + + // Set up the new setting field for the Test Mode toggle notice. + $notice = array( + 'stripe_connect_test_mode_toggle_notice' => array( + 'id' => 'stripe_connect_test_mode_toggle_notice', + 'desc' => '

    ' . __( '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.', 'easy-digital-downloads' ) . '

    ', + 'type' => 'stripe_connect_notice', + 'field_class' => 'edd-hidden', + ), + ); + + // Insert the new setting after the Test Mode checkbox. + $position = array_search( 'test_mode', array_keys( $settings['main'] ), true ); + + return array_merge( + array_slice( $settings['main'], $position, 1, true ), + $notice, + $settings + ); + } + + /** + * Outputs the payment method settings. + * + * @since 3.3.5 + * @param array $args The settings arguments. + */ + public static function render_payment_methods( $args ) { + $configuration = PaymentMethods::get_base_configuration(); + if ( ! $configuration ) { + esc_html_e( 'Unable to retrieve payment method configuration.', 'easy-digital-downloads' ); + return; + } + + $options = self::get_payment_method_options( $configuration ); + if ( empty( $options ) ) { + esc_html_e( 'No payment methods available.', 'easy-digital-downloads' ); + return; + } + + // Resort the $options array by the label. + uasort( + $options, + function ( $a, $b ) { + return strcasecmp( $a['label'], $b['label'] ); + } + ); + + $is_recurring_active = function_exists( 'edd_recurring' ); + ?> +
    +

    + +

    + +
    + + + + + + + +
    +
    + 'edd_settings[' . edd_sanitize_key( $args['id'] ) . ']', + 'options' => $options, + 'toggle' => true, + ) + ); + $multicheck->output(); + } + + /** + * Gets the layout setting. + * + * @since 3.3.5 + * @return array + */ + private function get_layout_setting() { + $layout_setting = array( + 'id' => 'stripe_payment_elements_layout', + 'name' => __( 'Payment Methods Style', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => array( + '' => __( 'Tabs', 'easy-digital-downloads' ), + 'accordion' => __( 'Accordion', 'easy-digital-downloads' ), + ), + 'desc' => __( 'Select the layout style for the Payment Methods section on the checkout form.', 'easy-digital-downloads' ), + 'class' => $this->is_payment_elements_mode() ? 'payment-elements-feature' : 'payment-elements-feature edd-hidden', + ); + if ( has_filter( 'edds_stripe_payment_elements_layout' ) ) { + $layout_setting['tooltip_title'] = __( 'Payment Methods Style', 'easy-digital-downloads' ); + $layout_setting['tooltip_desc'] = __( 'The Payment Methods Style setting is being overridden by a third-party plugin or custom code.', 'easy-digital-downloads' ); + $layout_setting['tooltip_dashicon'] = 'dashicons-warning'; + } + + return $layout_setting; + } + + /** + * Gets the debug mode setting. + * + * @since 3.3.5 + * @return array + */ + private function get_debug_mode_setting() { + if ( ! edd_is_debug_mode() ) { + return false; + } + if ( ! $this->is_gateway_settings_screen() ) { + return false; + } + + $debug_settings = array( + 'stripe_debug' => array( + 'id' => 'stripe_debug', + 'name' => __( 'Debugging Settings', 'easy-digital-downloads' ), + 'desc' => '
    ' . + '

    ' . __( '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.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( '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.', 'easy-digital-downloads' ) . '

    ' . + '

    ', + 'type' => 'descriptive_text', + ), + ); + + $card_elements_action = 'enable-card-elements'; + $card_elements_button_label = __( 'Enable access to Card Elements', 'easy-digital-downloads' ); + $card_elements_state_label = __( 'Access to Legacy Card Elements is Disabled', 'easy-digital-downloads' ); + $link_class = 'edd-button__toggle--disabled'; + if ( get_option( '_edds_legacy_elements_enabled', false ) ) { + $card_elements_action = 'disable-card-elements'; + $card_elements_button_label = __( 'Disable access to Card Elements', 'easy-digital-downloads' ); + $card_elements_state_label = __( 'Access to Legacy Card Elements is Enabled', 'easy-digital-downloads' ); + $link_class = 'edd-button__toggle--enabled'; + } + + $debug_settings['stripe_toggle_card_elements'] = array( + 'id' => 'stripe_toggle_card_elements', + 'name' => __( 'Toggle Card Elements', 'easy-digital-downloads' ), + 'type' => 'descriptive_text', + 'desc' => sprintf( + '%1$s' . $card_elements_button_label . '%2$s', + '', + '' + ) . '' . $card_elements_state_label . '
    ' . __( '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.', 'easy-digital-downloads' ), + ); + + return $debug_settings; + } + + /** + * Whether the Payment Elements mode is enabled. + * + * @since 3.3.5 + * @return bool + */ + private function is_payment_elements_mode() { + if ( is_null( $this->is_payment_elements_mode ) ) { + $this->is_payment_elements_mode = 'payment-elements' === edds_get_elements_mode(); + } + + return $this->is_payment_elements_mode; + } + + /** + * Whether the current screen is the gateway settings screen. + * + * @since 3.3.5 + * @return bool + */ + private function is_gateway_settings_screen() { + return function_exists( 'edd_is_admin_page' ) && edd_is_admin_page( 'settings', 'gateways' ); + } + + /** + * Gets the CSS class for the connected accounts. + * + * @since 3.3.5 + * @return string + */ + private function get_connected_class() { + return edd_stripe()->connect->is_connected ? '' : 'edd-hidden'; + } + + /** + * Gets the payment methods setting. + * + * @since 3.3.5 + * @return array|false + */ + private function get_payment_methods_setting() { + if ( ! $this->is_gateway_settings_screen() ) { + return false; + } + + if ( ! edd_stripe()->connect->is_connected ) { + return false; + } + + $configuration = PaymentMethods::get_base_configuration(); + if ( ! $configuration ) { + return false; + } + + return array( + 'id' => 'stripe_payment_methods', + 'name' => __( 'Payment Methods', 'easy-digital-downloads' ), + 'type' => 'hook', + 'class' => $this->is_payment_elements_mode() ? 'payment-elements-feature' : 'edd-hidden payment-elements-feature', + ); + } + + /** + * Gets the payment method options. + * + * @since 3.3.5 + * @param object $configuration The payment method configuration. + * @return array + */ + private static function get_payment_method_options( $configuration ) { + $options = array(); + $is_recurring_active = function_exists( 'edd_recurring' ); + $capabilities = self::get_account_capabilities(); + $capability_keys = wp_list_pluck( $capabilities, 'id' ); + $tooltip_args = array( + 'dashicon' => 'dashicons-admin-site', + ); + $is_test_mode = edd_is_test_mode(); + foreach ( $configuration as $method => $parameters ) { + $payment_method = PaymentMethods::get_payment_method( $method ); + if ( ! $payment_method ) { + continue; + } + $payment_types = __( 'One-time payments', 'easy-digital-downloads' ); + if ( $payment_method::$subscriptions ) { + $payment_types .= ', ' . __( 'subscriptions', 'easy-digital-downloads' ); + } + if ( $payment_method::$trials ) { + $payment_types .= ', ' . __( 'subscriptions with trials', 'easy-digital-downloads' ); + } + $description = array( + sprintf( + /* translators: 1: opening strong tag, 2: closing strong tag, 3: payment types */ + __( '%1$sPayment types:%2$s %3$s', 'easy-digital-downloads' ), + '', + '', + $payment_types + ), + ); + if ( ! empty( $payment_method::$currencies ) ) { + $description[] .= sprintf( + /* translators: 1: opening strong tag, 2: closing strong tag, 3: supported currencies */ + __( '%1$sSupported currencies:%2$s %3$s', 'easy-digital-downloads' ), + '', + '', + implode( ', ', $payment_method::$currencies ) + ); + } + if ( ! empty( $payment_method::$countries ) ) { + $description[] .= sprintf( + /* translators: 1: opening strong tag, 2: closing strong tag, 3: supported countries */ + __( '%1$sSupported countries:%2$s %3$s', 'easy-digital-downloads' ), + '', + '', + implode( ', ', array_map( 'strtoupper', $payment_method::$countries ) ) + ); + } + + $disabled = 'card' === $method || empty( $parameters['display_preference']['overridable'] ); + if ( in_array( "{$method}_payments", $capability_keys, true ) ) { + $capability = $capabilities[ array_search( "{$method}_payments", $capability_keys, true ) ]; + if ( $capability instanceof \EDD\Vendor\Stripe\Capability && 'active' !== $capability->status ) { + $disabled = ! $is_test_mode; + $description[] = $is_test_mode ? + sprintf( + /* translators: 1: opening strong tag, 2: closing strong tag */ + __( '%1$sStatus:%2$s You can test this, but to use it in Live mode, you must request access to this payment method from your Stripe account.', 'easy-digital-downloads' ), + '', + '' + ) : + sprintf( + /* translators: 1: opening strong tag, 2: closing strong tag */ + __( '%1$sStatus:%2$s You must request access to this payment method from your Stripe account.', 'easy-digital-downloads' ), + '', + '' + ); + } + } + + $options[ $method ] = array( + 'label' => $payment_method::get_label(), + 'disabled' => $disabled, + 'checked' => ! empty( $parameters['available'] ) && 'on' === $parameters['display_preference']['value'], + 'icon' => $payment_method::get_icon(), + 'classes' => self::get_payment_method_classes( $payment_method ), + ); + + if ( ! empty( $description ) ) { + $tooltip_args['content'] = implode( '

    ', $description ); + $options[ $method ]['tooltip'] = $tooltip_args; + } + } + + return $options; + } + + /** + * Gets the account capabilities. + * + * @since 3.3.5 + * @return array + */ + private static function get_account_capabilities() { + $transient = new \EDD\Utils\Transient( 'edd_stripe_account_capabilities', '+1 week' ); + $capabilities = $transient->get(); + if ( $capabilities ) { + return $capabilities; + } + + try { + $stripe = edds_api_request( + 'account', + 'allCapabilities', + edd_stripe()->connect->get_connect_id() + ); + } catch ( \Exception $e ) { + return array(); + } + + $transient->set( $stripe->data ); + + return $stripe->data; + } + + /** + * Gets the CSS classes for the payment method toggle. + * + * @since 3.3.5 + * @param \EDD\Gateways\Stripe\PaymentMethods\Method $payment_method The payment method. + * @return array + */ + private static function get_payment_method_classes( $payment_method ) { + $classes = array( + 'edd-stripe-payment-method', + ); + if ( $payment_method::$scope ) { + $classes[] = "edd-stripe-payment-method--{$payment_method::$scope}"; + } + if ( $payment_method::$subscriptions ) { + $classes[] = 'edd-stripe-payment-method--subscriptions'; + } + if ( $payment_method::$trials ) { + $classes[] = 'edd-stripe-payment-method--trials'; + } + + return $classes; + } +} diff --git a/src/Gateways/Stripe/ApplicationFee.php b/src/Gateways/Stripe/ApplicationFee.php new file mode 100644 index 00000000000..b64d262bf90 --- /dev/null +++ b/src/Gateways/Stripe/ApplicationFee.php @@ -0,0 +1,279 @@ +connect()->get_connect_id() ) ) { + $this->status = 'Not Connected'; + return false; + } + + // Not in country that supports the fees (always false). + if ( true !== edds_stripe_connect_account_country_supports_application_fees() ) { + $this->status = 'Not Supported'; + return false; + } + + // No license (always true). + if ( ! $this->get_license() ) { + $this->status = 'No License'; + return true; + } + + // The license is valid (always false). + if ( $this->license->is_license_valid() ) { + $this->status = 'Valid License'; + return false; + } + + // It's a new pro install, so false for now. + if ( $this->license->is_in_new_install_grace_period() ) { + $this->status = 'New Install Grace Period'; + return false; + } + + // License is expired, but in the grace period, so false for now. + if ( $this->license->is_in_grace_period() ) { + $this->status = 'Grace Period'; + return false; + } + + return true; + } + + /** + * Gets the application fee amount. + * This amount is in cents and should not include a decimal. + * This method does not check if the application fee should be added; it only calculates it. + * + * @since 3.2.0 + * @param float $amount The amount. + * + * @return float + */ + public function get_application_fee_amount( $amount ) { + return round( $amount * ( $this->get_application_fee_percentage() / 100 ), 0 ); + } + + /** + * Gets the application fee message for the settings screen. + * + * @since 3.2.0 + * @return string + */ + public function get_fee_message() { + if ( $this->get_license() && $this->license->is_license_valid() ) { + return ''; + } + + // Message that shows before connecting to Stripe. + if ( ! edd_doing_ajax() ) { + return $this->get_initial_connect_message() . ' '; + } + + $message = sprintf( + /* translators: 1: opening strong tag, 2: closing strong tag, 3: the message explaining the application fee (eg "3% per-transaction fee + Stripe fees"). */ + __( '%1$sPay as you go pricing:%2$s %3$s.', 'easy-digital-downloads' ), + '', + '', + $this->get_base_fee_message() + ); + if ( empty( $this->license->license_data->key ) && ! edds_is_pro() && ! edd_is_pro() ) { + $message .= ' ' . sprintf( + /* translators: Replacements are for the html wrappers for the phrse Upgrade to Pro and should not be translated. */ + __( '%1$sUpgrade to Pro%2$s to remove transaction fees.', 'easy-digital-downloads' ), + '', + '' + ); + } else { + $message .= $this->get_license_message(); + } + + return $message; + } + + /** + * Resets the license object. + * + * @since 3.2.0 + */ + public function reset_license() { + $this->license = null; + $this->status = ''; + } + + /** + * Gets the status of the Stripe connection. + * + * @since 3.2.7 + * @return string + */ + public function get_status() { + if ( empty( $this->status ) ) { + $this->has_application_fee(); + } + + $status = 'License Data Missing'; + if ( empty( $this->status ) ) { + $license = $this->get_license(); + if ( ! empty( $license->license_data->error ) ) { + $status = 'License Error: ' . $license->license_data->error; + } else { + $pro_license = new \EDD\Licensing\License( 'pro' ); + if ( ! empty( $pro_license->error ) ) { + $status = 'License Error: ' . $pro_license->error; + } + } + $this->status = $status; + } + + return $this->status; + } + + /** + * Gets the base fee message string. + * + * @since 3.2.0 + * @return string + */ + private function get_base_fee_message() { + return sprintf( + /* translators: the application fee percentage. */ + __( '%d%% per-transaction fee + Stripe fees', 'easy-digital-downloads' ), + $this->get_application_fee_percentage() + ); + } + + /** + * Gets the application fee percentage. + * + * @since 3.2.0 + * @return int + */ + private function get_application_fee_percentage() { + return 3; + } + + /** + * Gets the license object. + * + * @since 3.2.0 + * @return \EDD\Gateways\Stripe\License + */ + private function get_license() { + if ( is_null( $this->license ) ) { + $this->license = new License(); + } + + return $this->license; + } + + /** + * Gets the initial Stripe connect message. + * + * @since 3.2.0 + * @return string + */ + private function get_initial_connect_message() { + if ( ! $this->get_license() || empty( $this->license->license_data->key ) ) { + return sprintf( + /* translators: the message explaining the application fee (eg "3% per-transaction fee + Stripe fees") */ + __( 'Connect with Stripe for pay as you go pricing: %s.', 'easy-digital-downloads' ), + $this->get_base_fee_message() + ); + } + + // Stripe Pro is active, but the license is not valid. + return sprintf( + /* translators: the message explaining the application fee (eg "3% per-transaction fee + Stripe fees") */ + __( 'Connect with Stripe for pay as you go pricing: %s. Activate your license to remove the per-transaction fee.', 'easy-digital-downloads' ), + $this->get_base_fee_message() + ); + } + + /** + * Gets the license message. + * + * @since 3.2.0 + * @return string + */ + private function get_license_message() { + + $license = $this->get_license(); + + // For a new install, show the new install grace period message. + $new_install_grace = $license->get_new_install_grace_period_end_date(); + if ( $new_install_grace ) { + return ' ' . sprintf( + /* translators: the date the grace period ends */ + __( 'You are in a grace period for your new license. Activate your license by %s to prevent additional fees.', 'easy-digital-downloads' ), + $new_install_grace + ); + } + + if ( $license->is_expired() ) { + // For an expired license within the grace period, show the grace period message. + $grace_period_end_date = $license->get_grace_period_end_date(); + if ( $grace_period_end_date ) { + return ' ' . sprintf( + /* translators: 1: opening link tag, do not translate, 2: closing link tag, do not translate; 3. the date the grace period ends */ + __( 'Your license has expired, but you are in a grace period. %1$sRenew your license key%2$s before %3$s to prevent being charged additional transaction fees.', 'easy-digital-downloads' ), + '', + '', + $grace_period_end_date + ); + } + + return ' ' . sprintf( + /* translators: 1: the date the license expired, 2: opening link tag, do not translate, 3: closing link tag, do not translate */ + __( 'Your license expired on %1$s. %2$sRenew your license%3$s to prevent additional fees.', 'easy-digital-downloads' ), + $license->get_expiration_date(), + '', + '' + ); + } + + return ' ' . sprintf( + /* translators: opening link tag, do not translate; closing link tag, do not translate */ + __( '%1$sActivate or upgrade your license%2$s to prevent additional fees.', 'easy-digital-downloads' ), + '', + '' + ); + } +} diff --git a/src/Gateways/Stripe/Checkout/Complete.php b/src/Gateways/Stripe/Checkout/Complete.php new file mode 100644 index 00000000000..a4f5fbda686 --- /dev/null +++ b/src/Gateways/Stripe/Checkout/Complete.php @@ -0,0 +1,305 @@ +rate_limiting->has_hit_card_error_limit() ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1001: An error occurred, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'Rate limit reached during order completion.' + ); + } + + // This must happen in the Checkout flow, so validate the Checkout nonce. + if ( false === edds_verify() ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1002: An error occurred, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'Nonce verification failed during order completion.' + ); + } + + $intent = $this->get_intent(); + + // Get the existing order if one was created. + if ( ! empty( $intent->metadata->edd_payment_id ) ) { + $order_id = $intent->metadata->edd_payment_id; + } else { + // Create a new order. + $order_builder = new Order(); + $order_id = $order_builder->create( $intent ); + } + self::mark_complete( edd_get_order( $order_id ), $intent ); + + $order = edd_get_order( $order_id ); + + wp_send_json_success( + array( + 'intent' => $intent, + 'nonce' => wp_create_nonce( 'edd-process-checkout' ), + 'status' => $order->status, + 'order_id' => $order->id, + ) + ); + + // Catch gateway processing errors. + } catch ( \EDD_Stripe_Gateway_Exception $e ) { + // Increase the rate limit count when something goes wrong mid-process. + edd_stripe()->rate_limiting->increment_card_error_count(); + + if ( true === $e->hasLogMessage() ) { + edd_record_gateway_error( + esc_html__( 'Stripe Error', 'easy-digital-downloads' ), + $e->getLogMessage(), + 0 + ); + } + + wp_send_json_error( + array( + 'message' => esc_html( $e->getMessage() ), + ) + ); + + // Catch any remaining error. + } catch ( \Exception $e ) { + wp_send_json_error( + array( + 'message' => esc_html( $e->getMessage() ), + ) + ); + } + } + + /** + * Mark the order as complete. + * + * @since 3.3.5 + * + * @param \EDD\Orders\Order $order The EDD Order object. + * @param \Stripe\PaymentIntent|\Stripe\SetupIntent $intent The Stripe Intent object. + * @return void + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + public static function mark_complete( $order, $intent ) { + if ( 'succeeded' !== $intent->status ) { + return; + } + $final_status = edds_is_preapprove_enabled() ? 'preapproval' : 'complete'; + if ( $final_status === $order->status ) { + return; + } + + $updated = edd_update_order_status( $order->id, $final_status ); + if ( $updated ) { + + if ( 'setup_intent' !== $intent['object'] ) { + $charge_id = sanitize_text_field( current( $intent['charges']['data'] )['id'] ); + + edd_add_note( + array( + 'object_id' => $order->id, + 'content' => 'Stripe Charge ID: ' . $charge_id, + 'user_id' => is_admin() ? get_current_user_id() : 0, + 'object_type' => 'order', + ) + ); + + $order_transaction = edd_get_order_transaction_by( 'object_id', $order->id ); + if ( ! empty( $order_transaction ) ) { + edd_update_order_transaction( + $order_transaction->id, + array( + 'transaction_id' => sanitize_text_field( $charge_id ), + 'status' => 'complete', + ) + ); + } + } + + self::do_legacy_complete_hook( $order, $intent ); + + /** + * Allows further processing after a order is completed. + * + * Sends back just the Intent ID to avoid needing always retrieve + * the intent in this step, which has been transformed via JSON, + * and is no longer a \Stripe\PaymentIntent + * + * @since 2.9.0 + * + * @param \EDD\Orders\Order $order The EDD Order object. + * @param string $intent_id Stripe Intent ID. + */ + do_action( 'edds_order_complete', $order, $intent['id'] ); + + // Empty cart. + edd_empty_cart(); + } else { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1007: An error occurred completing the order, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'Unable to complete order. (1007)' + ); + } + } + + /** + * Mark an order complete based on a successful charge. This may used for + * slower payment methods like bank transfers. + * + * This method does not run the legacy complete hook. + * + * @since 3.3.5 + * @param EDD\Orders\Order $order The EDD Order object. + * @param EDD\Vendor\Stripe\Charge $charge The Stripe Charge object. + * @return void + */ + public static function mark_complete_from_charge( $order, $charge ) { + $final_status = edds_is_preapprove_enabled() ? 'preapproval' : 'complete'; + if ( $final_status === $order->status ) { + return; + } + + $updated = edd_update_order_status( $order->id, $final_status ); + if ( ! $updated ) { + return; + } + + edd_add_note( + array( + 'object_id' => $order->id, + 'content' => 'Stripe Charge ID: ' . $charge->id, + 'user_id' => 0, + 'object_type' => 'order', + ) + ); + + /** + * Allows further processing after an order is completed. + * + * @since 3.3.5 + * + * @param \EDD\Orders\Order $order The EDD Order object. + * @param string $intent_id Stripe Payment Intent ID. + */ + do_action( 'edds_order_complete', $order, $charge->payment_intent ); + } + + /** + * Retrieve the Intent ID. + * + * @since 3.3.5 + * @return string + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + private function get_intent_id() { + $intent_id = isset( $_REQUEST['intent_id'] ) ? $_REQUEST['intent_id'] : ''; + if ( empty( $intent_id ) ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1003: An error occurred, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'Unable to retrieve Intent data during payment completion.' + ); + } + + return $intent_id; + } + + /** + * Retrieve the Intent. + * + * @since 3.3.5 + * @return \Stripe\PaymentIntent|\Stripe\SetupIntent + */ + private function get_intent() { + $request = 'SetupIntent' === $_REQUEST['intent_type'] ? 'SetupIntent' : 'PaymentIntent'; + $intent_id = $this->get_intent_id(); + $intent = edds_api_request( $request, 'retrieve', $intent_id ); + + if ( ! $intent ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1005: An error occurred, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'Missing PaymentIntent ' . $intent_id . ' during order completion.' + ); + } + + return $intent; + } + + /** + * Performs the legacy complete hook for the Stripe checkout. + * + * @param object $order The order object. + * @param object $intent The intent object. + * @return void + */ + private static function do_legacy_complete_hook( $order, $intent ) { + if ( ! has_action( 'edds_payment_complete' ) ) { + return; + } + + // Load up an EDD Payment record here, in the event there is something hooking into it. + $payment = new \EDD_Payment( $order->id ); + + /** + * Allows further processing after a payment is completed. + * + * Sends back just the Intent ID to avoid needing always retrieve + * the intent in this step, which has been transformed via JSON, + * and is no longer a \Stripe\PaymentIntent + * + * NOTE TO DEVELOPERS: Only hook into one of these complete hooks. Using both will result in + * unexpected double processing. + * + * @since 2.7.0 + * + * @param \EDD_Payment $payment EDD Payment. + * @param string $intent_id Stripe Intent ID. + */ + do_action( 'edds_payment_complete', $payment, $intent['id'] ); + } +} diff --git a/src/Gateways/Stripe/Checkout/Confirmation.php b/src/Gateways/Stripe/Checkout/Confirmation.php new file mode 100644 index 00000000000..b6b1dfe109c --- /dev/null +++ b/src/Gateways/Stripe/Checkout/Confirmation.php @@ -0,0 +1,93 @@ +status && $purchase_session ) { + edd_redirect( edd_get_success_page_uri() ); + } + + $args = array( + 'payment-confirmation' => 'stripe', + ); + + // This tells the confirmation page to show the processing template. + if ( $purchase_session && 'pending' === $order->status ) { + $args['status'] = 'processing'; + } + + edd_redirect( + add_query_arg( + $args, + edd_get_success_page_uri() + ) + ); + } + + /** + * Get the order from the PaymentIntent. + * + * @since 3.3.5 + * @param string $intent_id PaymentIntent ID. + * @return \EDD\Orders\Order|false + */ + private static function get_order( $intent_id ) { + $intent = Intents::get( $intent_id ); + if ( ! $intent ) { + return false; + } + + // Only process successful or processing intents. + if ( ! in_array( $intent->status, array( 'succeeded', 'processing' ), true ) ) { + return false; + } + + // Get the order ID from the metadata (edd_payment_id). + $metadata = $intent->metadata; + if ( ! isset( $metadata->edd_payment_id ) ) { + return false; + } + + $order = edd_get_order( $metadata->edd_payment_id ); + if ( ! $order ) { + return false; + } + + // If the intent is complete but the order is not, mark it complete. + if ( 'succeeded' === $intent->status && 'complete' !== $order->status ) { + Complete::mark_complete( $order, $intent ); + + return edd_get_order( $order->id ); + } + + return $order; + } +} diff --git a/src/Gateways/Stripe/Checkout/Form.php b/src/Gateways/Stripe/Checkout/Form.php new file mode 100644 index 00000000000..b3212038d2c --- /dev/null +++ b/src/Gateways/Stripe/Checkout/Form.php @@ -0,0 +1,635 @@ +purchase_data = $purchase_data; + } + + /** + * Process the checkout form. + * + * This method is responsible for processing the checkout form and performing any necessary actions. + * It is called when the form is submitted. + * + * @since 3.3.5 + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + public function process() { + // Catch a straight to gateway request. + // Remove the error set by the "gateway mismatch" and allow the redirect. + if ( isset( $_REQUEST['edd_action'] ) && 'straight_to_gateway' === $_REQUEST['edd_action'] ) { + edd_unset_error( 'edd-straight-to-gateway-error' ); + edd_send_back_to_checkout(); + } + + try { + if ( edd_stripe()->rate_limiting->has_hit_card_error_limit() ) { + throw new \EDD_Stripe_Gateway_Exception( edd_stripe()->rate_limiting->get_rate_limit_error_message() ); + } + + $payment_method = $this->get_payment_method(); + if ( empty( $payment_method ) ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1008: An error occurred, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'No payment method provided.' + ); + } + + /** + * Allows processing before an Intent is created. + * + * @since 2.7.0 + * + * @param array $purchase_data Purchase data. + */ + do_action( 'edds_pre_process_purchase_form', $this->purchase_data ); + + $amount = $this->get_amount(); + $customer = $this->get_customer(); + + /** + * Allows processing before an Intent is created, but + * after a \Stripe\Customer is available. + * + * @since 2.7.0 + * + * @param array $purchase_data Purchase data. + * @param \Stripe\Customer $customer Stripe Customer object. + */ + do_action( 'edds_process_purchase_form_before_intent', $this->purchase_data, $customer ); + + // Shared Intent arguments. + $intent_args = $this->get_intent_args( $payment_method ); + + // We need the intent type later, so we'll set it here. + $intent_type = ( 0 === $amount || edds_is_preapprove_enabled() ) ? 'SetupIntent' : 'PaymentIntent'; + + // Update the intent arguments based on the intent type. + if ( 'SetupIntent' === $intent_type ) { + $intent_args = $this->update_setup_intent_args( $intent_args ); + } else { + $intent_args = $this->update_payment_intent_args( $intent_args, $payment_method['type'] ); + } + + if ( edd_stripe()->application_fee->has_application_fee() ) { + $application_fee = edd_stripe()->application_fee->get_application_fee_amount( $amount ); + if ( ! empty( $application_fee ) ) { + $intent_args['application_fee_amount'] = $application_fee; + } + } + + $new_fingerprint = md5( json_encode( $intent_args ) ); + + // Only update the intent, and process this further if we've made changes to the intent. + if ( $this->existing_intent_unchanged( $new_fingerprint ) ) { + return wp_send_json_success( + array( + 'intent_id' => $this->intent->id, + 'client_secret' => $this->intent->client_secret, + 'intent_type' => $this->intent_type, + 'token' => wp_create_nonce( 'edd-process-checkout' ), + 'intent_fingerprint' => $new_fingerprint, + 'intent_changed' => 0, + ) + ); + } + + /** + * If purchasing a subscription with a card, we need to add the subscription mandate data. + * + * This will ensure that any cards that require mandates like INR payments or India based cards will correctly add + * the mandates necessary for recurring payments. + * + * We do this after we check for an existing intent ID, because the mandate data will change depending on the 'timestamp'. + */ + if ( $this->is_mandate_required( $payment_method ) ) { + require_once EDDS_PLUGIN_DIR . 'includes/utils/class-edd-stripe-mandates.php'; + $mandates = new \EDD_Stripe_Mandates( $this->purchase_data, $intent_type ); + + // Add the mandate options to the intent arguments. + $intent_args['payment_method_options']['card']['mandate_options'] = $mandates->mandate_options; + } + + if ( ! empty( $this->existing_intent ) && ! empty( $this->intent ) ) { + // Existing intents need to not have the automatic_payment_methods flag set. + if ( ! empty( $intent_args['automatic_payment_methods'] ) ) { + unset( $intent_args['automatic_payment_methods'] ); + } + + edds_api_request( $intent_type, 'update', $this->intent->id, $intent_args ); + $intent = edds_api_request( $intent_type, 'retrieve', $this->intent->id ); + } else { + $intent = edds_api_request( $intent_type, 'create', $intent_args ); + $this->maybe_create_order( $payment_method['type'], $intent ); + } + + /** + * Allows further processing after an Intent is created. + * + * @since 2.7.0 + * + * @param array $purchase_data Purchase data. + * @param \Stripe\PaymentIntent|\Stripe\SetupIntent $intent Created Stripe Intent. + * @param int $payment_id EDD Payment ID. + */ + do_action( 'edds_process_purchase_form', $this->purchase_data, $intent ); + + return wp_send_json_success( + array( + 'intent_id' => $intent->id, + 'client_secret' => $intent->client_secret, + 'intent_type' => $intent_type, + 'token' => wp_create_nonce( 'edd-process-checkout' ), + 'intent_fingerprint' => $new_fingerprint, + 'intent_changed' => 1, + ) + ); + + } catch ( \EDD\Vendor\Stripe\Exception\ApiErrorException $e ) { + $error = $e->getJsonBody()['error']; + + // Record error in log. + edd_record_gateway_error( + esc_html__( 'Stripe Error 002', 'easy-digital-downloads' ), + sprintf( + /* translators: %s: Error message */ + esc_html__( 'There was an error while processing a Stripe payment. Order data: %s', 'easy-digital-downloads' ), + wp_json_encode( $error['message'] ) + ), + 0 + ); + + return wp_send_json_error( + array( + 'message' => esc_html( + edds_get_localized_error_message( $error['type'], $error['message'] ) + ), + ) + ); + + // Catch gateway processing errors. + } catch ( \EDD_Stripe_Gateway_Exception $e ) { + if ( true === $e->hasLogMessage() ) { + edd_record_gateway_error( + esc_html__( 'Stripe Error 003', 'easy-digital-downloads' ), + $e->getLogMessage(), + 0 + ); + } + + return wp_send_json_error( + array( + 'message' => esc_html( $e->getMessage() ), + ) + ); + + // Catch any remaining error. + } catch ( \Exception $e ) { + + return wp_send_json_error( + array( + 'message' => esc_html( $e->getMessage() ), + ) + ); + } + } + + /** + * Retrieves the payment method for the Stripe checkout form. + * + * @since 3.3.5 + * @return array|false The payment method, or false if not found. + */ + private function get_payment_method() { + if ( ! empty( $_REQUEST['payment_method'] ) && is_array( $_REQUEST['payment_method'] ) ) { + return $_REQUEST['payment_method']; + } + + return false; + } + + /** + * Retrieves the amount for the Stripe checkout form. + * + * @since 3.3.5 + * @return int The amount for the Stripe checkout form. + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + private function get_amount() { + if ( ! is_null( $this->amount ) ) { + return $this->amount; + } + + if ( edds_is_zero_decimal_currency() ) { + $this->amount = $this->purchase_data['price']; + } else { + $this->amount = round( $this->purchase_data['price'] * 100, 0 ); + } + + if ( ! $this->cart_contains_subscription() ) { + return $this->amount; + } + + if ( ! edd_gateway_supports_cart_contents( 'stripe' ) ) { + throw new \EDD_Stripe_Gateway_Exception( edds_get_single_subscription_cart_error() ); + } + + if ( $this->cart_has_free_trial() ) { + $this->amount = 0; + } + + global $edd_recurring_stripe; + remove_filter( 'edds_create_payment_intent_args', array( $edd_recurring_stripe, 'create_payment_intent_args' ), 10, 2 ); + + return $this->amount; + } + + /** + * Checks if the cart contains a subscription. + * + * @since 3.3.5 + * @return bool Returns true if the cart contains a subscription, false otherwise. + */ + private function cart_contains_subscription() { + if ( is_null( $this->cart_contains_subscription ) ) { + $this->cart_contains_subscription = (bool) ( function_exists( 'edd_recurring' ) && edd_recurring()->cart_contains_recurring() ); + } + + return $this->cart_contains_subscription; + } + + /** + * Checks if the cart has a free trial. + * + * @since 3.3.5 + * @return bool Returns true if the cart has a free trial, false otherwise. + */ + private function cart_has_free_trial() { + return $this->cart_contains_subscription() && edd_recurring()->cart_has_free_trial(); + } + + /** + * Retrieves the payment items for the Stripe checkout form. + * + * @since 3.3.5 + * @return array The payment items for the Stripe checkout form. + */ + private function get_payment_items() { + $payment_items = array(); + + // Create a list of {$download_id}_{$price_id}. + foreach ( $this->purchase_data['cart_details'] as $item ) { + $item_id = $item['id']; + if ( isset( $item['item_number']['options']['price_id'] ) ) { + $price_id = $item['item_number']['options']['price_id']; + $item_id .= '_' . intval( $price_id ); + } + + $payment_items[] = $item_id; + } + + return $payment_items; + } + + /** + * Retrieves the customer associated with the Stripe checkout form. + * + * @since 3.3.5 + * @return mixed The customer object or null if not found. + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + private function get_customer() { + if ( ! empty( $this->customer ) ) { + return $this->customer; + } + + $customer = false; + $intent = $this->get_intent(); + if ( $intent && ! empty( $this->intent->customer ) ) { + $this->existing_intent = true; + $customer = edds_get_stripe_customer( $this->intent->customer, array() ); + } + + // We didn't have a customer on the existing intent. Make a new one. + if ( empty( $customer ) ) { + // Retrieves or creates a Stripe Customer. + $customer = edds_checkout_setup_customer( $this->purchase_data ); + } + + if ( ! $customer ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Unable to create customer. Please try again.', + 'easy-digital-downloads' + ) + ); + } + + $this->customer = $customer; + + return $customer; + } + + /** + * Retrieves the intent associated with the Stripe checkout form. + * + * @since 3.3.5 + * @return mixed The intent object or null if not found. + */ + private function get_intent() { + if ( ! is_null( $this->intent ) ) { + return $this->intent; + } + + if ( ! empty( $_REQUEST['intent_id'] ) && ! empty( $_REQUEST['intent_fingerprint'] ) ) { + $this->intent = edds_api_request( $_REQUEST['intent_type'], 'retrieve', $_REQUEST['intent_id'] ); + } + + return $this->intent; + } + + /** + * Retrieves the future usage for the Stripe checkout form. + * + * @since 3.3.5 + * @param string $type The type of payment method. + * @return string The future usage for the Stripe checkout form. + */ + private function get_future_usage( $type ) { + if ( 'link' === $type || $this->cart_contains_subscription() ) { + return true; + } + + return false; + } + + /** + * Retrieves the intent arguments for the Stripe checkout form. + * + * @since 3.3.5 + * @param array $payment_method The payment method. + * @return array The intent arguments for the Stripe checkout form. + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + private function get_intent_args( $payment_method ) { + $customer = $this->get_customer(); + + $intent_args = array( + 'customer' => $customer->id, + 'metadata' => array( + 'email' => esc_html( $this->purchase_data['user_info']['email'] ), + 'edd_payment_subtotal' => esc_html( $this->purchase_data['subtotal'] ), + 'edd_payment_discount' => esc_html( $this->purchase_data['discount'] ), + 'edd_payment_tax' => esc_html( $this->purchase_data['tax'] ), + 'edd_payment_tax_rate' => esc_html( $this->purchase_data['tax_rate'] ), + 'edd_payment_fees' => esc_html( edd_get_cart_fee_total() ), + 'edd_payment_total' => esc_html( $this->purchase_data['price'] ), + 'edd_payment_items' => esc_html( implode( ', ', $this->get_payment_items() ) ), + 'zero_decimal_amount' => $this->get_amount(), + ), + 'payment_method' => sanitize_text_field( $payment_method['id'] ), + 'automatic_payment_methods' => array( 'enabled' => true ), + 'description' => edds_get_payment_description( $this->purchase_data['cart_details'] ), + + ); + + $payment_method_configuration = $this->get_payment_method_configuration(); + if ( ! empty( $payment_method_configuration ) ) { + $intent_args['payment_method_configuration'] = $payment_method_configuration; + } + + return $intent_args; + } + + /** + * Maybe creates an order for the Stripe checkout form. + * + * @since 3.3.5 + * @param string $payment_method_type The payment method type. + * @return int|false The order ID, or false if not created. + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + private function maybe_create_order( $payment_method_type, $intent ) { + if ( in_array( $payment_method_type, array( 'card', 'link' ), true ) ) { + return false; + } + + $order = new Order(); + $order_id = $order->create( $intent ); + if ( ! $order_id ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1009: An error occurred, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'Unable to insert order record. (1009)' + ); + } + edd_update_order_meta( $order_id, 'stripe_payment_method_type', $payment_method_type ); + + return $order_id; + } + + /** + * Checks if the intent needs more processing (likely due to args changing). + * + * @since 3.3.5 + * @param array $new_fingerprint The new fingerprint. + * @return bool Returns true if the intent needs more processing, false otherwise. + */ + private function existing_intent_unchanged( $new_fingerprint ) { + if ( ! $this->get_intent() || empty( $_REQUEST['intent_fingerprint'] ) ) { + return false; + } + + return hash_equals( $_REQUEST['intent_fingerprint'], $new_fingerprint ); + } + + /** + * Updates the SetupIntent arguments for the Stripe checkout form. + * + * @since 3.3.5 + * @param array $intent_args The intent arguments. + * @return array The updated SetupIntent arguments for the Stripe checkout form. + */ + private function update_setup_intent_args( $intent_args ) { + $intent_args['usage'] = 'off_session'; + + /** + * BETA Functionality. + * + * Sending the automatic_payment_methods flag to the SetupIntent is a beta feature that we have to enable via an API version + * + * @link https://stripe.com/docs/payments/defer-intent-creation?type=setup#create-intent + */ + add_action( + 'edds_pre_stripe_api_request', + function () { + \EDD\Vendor\Stripe\Stripe::setApiVersion( '2018-09-24;automatic_payment_methods_beta=v1' ); + }, + 11 + ); + + /** + * Filters the arguments used to create a SetupIntent. + * + * @since 2.7.0 + * + * @param array $intent_args SetupIntent arguments. + * @param array $purchase_data The purchase data. + */ + return apply_filters( 'edds_create_setup_intent_args', $intent_args, $this->purchase_data ); + } + + /** + * Updates the PaymentIntent arguments for the Stripe checkout form. + * + * @since 3.3.5 + * @param array $intent_args The intent arguments. + * @param string $payment_method_type The payment method type. + * @return array The updated PaymentIntent arguments for the Stripe checkout form. + */ + private function update_payment_intent_args( $intent_args, $payment_method_type ) { + + $intent_args['amount'] = $this->get_amount(); + $intent_args['currency'] = edd_get_currency(); + + // If this is a card payment method, we need to add the statement descriptor suffix. + if ( 'card' === $payment_method_type ) { + $statement_descriptor_suffix = \EDD\Gateways\Stripe\StatementDescriptor::sanitize_suffix( $intent_args['description'] ); + if ( ! empty( $statement_descriptor_suffix ) ) { + $intent_args['statement_descriptor_suffix'] = $statement_descriptor_suffix; + } + } elseif ( 'wechat_pay' === $payment_method_type ) { + $intent_args['payment_method_options']['wechat_pay']['client'] = 'web'; + } + + if ( $this->get_future_usage( $payment_method_type ) ) { + $intent_args['setup_future_usage'] = 'off_session'; + } + + /** + * Filters the arguments used to create a PaymentIntent. + * + * @since 2.7.0 + * + * @param array $intent_args PaymentIntent arguments. + * @param array $purchase_data The purchase data. + */ + $intent_args = apply_filters( 'edds_create_payment_intent_args', $intent_args, $this->purchase_data ); + + /** + * As of Feb 1, 2024, Stripe no longer allows Statement Descriptors for PaymentIntents with cards. + * + * @since 3.2.8 + * + * Because of this EDD will always default to the Stripe settings, by sending no statement descriptor. + * If a developer was altering it with this method, then the filters will no longer work, in order to avoid + * failed payments from happening. + * + * Dynamic statement descriptors can be enabled by including the Order ID in the EDD Stripe + */ + if ( isset( $intent_args['statement_descriptor'] ) ) { + unset( $intent_args['statement_descriptor'] ); + } + + return $intent_args; + } + + /** + * Retrieves the payment method configuration for the Stripe checkout form. + * + * @since 3.3.5 + * @return array The payment method configuration for the Stripe checkout form. + */ + private function get_payment_method_configuration() { + $type = ''; + if ( $this->cart_contains_subscription() ) { + $type = 'subscriptions'; + if ( $this->cart_has_free_trial() ) { + $type = 'trials'; + } + } + + return \EDD\Gateways\Stripe\PaymentMethods::get_configuration_id( $type ); + } + + /** + * Checks if a mandate is required for the Stripe checkout form. + * + * @since 3.3.6 + * @param array $payment_method The payment method. + * @return bool Returns true if a mandate is required, false otherwise. + */ + private function is_mandate_required( $payment_method ) { + /** + * Filters whether a mandate is required for the Stripe checkout form. + * + * @since 3.3.6 + * @param bool $mandate_required Whether a mandate is required. + * @param array $payment_method The payment method. + */ + return apply_filters( 'edds_mandate_required', 'card' === $payment_method['type'], $payment_method ); + } +} diff --git a/src/Gateways/Stripe/Checkout/Order.php b/src/Gateways/Stripe/Checkout/Order.php new file mode 100644 index 00000000000..8ec6f16b690 --- /dev/null +++ b/src/Gateways/Stripe/Checkout/Order.php @@ -0,0 +1,256 @@ +purchase_data = $this->get_purchase_data(); + } + + /** + * Create the order. + * + * @since 3.3.5 + * @return false|int The order ID on success, false on failure. + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + public function create( $intent ) { + // Ensure $_COOKIE is available without a new HTTP request. + if ( \EDD\Checkout\AutoRegister::is_enabled() ) { + add_action( 'set_logged_in_cookie', 'edd_set_logged_in_cookie' ); + } + + $order_id = edd_build_order( $this->purchase_data ); + if ( false === $order_id ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1006: An error occurred, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'Unable to insert order record. (1006)' + ); + } + + $this->update_intent( $order_id, $intent ); + + return $order_id; + } + + /** + * Update the order and the Intent. + * + * @since 3.3.5 + * @param int $order_id The order ID. + * @param mixed $intent The Intent. + * @return int The order ID. + */ + public function update_intent( $order_id, $intent ) { + + $order = edd_get_order( $order_id ); + if ( ! $order ) { + return $order_id; + } + + if ( edd_get_order_meta( $order->id, '_edds_stripe_payment_intent_id', true ) ) { + return $order_id; + } + + // Retrieve the relevant Intent. + if ( 'setup_intent' === $intent->object ) { + $intent = $this->maybe_update_intent( $intent, $order->id, 'SetupIntent' ); + + edd_add_order_meta( + $order->id, + '_edds_stripe_setup_intent_id', + $intent->id + ); + } else { + $intent = $this->maybe_update_intent( $intent, $order->id, 'PaymentIntent' ); + + edd_add_order_meta( + $order->id, + '_edds_stripe_payment_intent_id', + $intent->id + ); + + // Use Intent ID for temporary transaction ID. + // It will be updated when a charge is available. + edd_add_order_transaction( + array( + 'object_id' => $order->id, + 'object_type' => 'order', + 'transaction_id' => sanitize_text_field( $intent->id ), + 'gateway' => 'stripe', + 'status' => 'pending', + 'total' => $order->total, + ) + ); + } + + // Adds the customer ID to the order meta. + edd_update_order_meta( $order->id, '_edds_stripe_customer_id', $intent->customer ); + + if ( ! empty( $intent->charges->data ) ) { + foreach ( $intent->charges->data as $charge ) { + $this->add_payment_method( $order->id, $charge->payment_method_details ); + if ( empty( $charge->payment_method_details->card->mandate ) ) { + continue; + } + + // The returned Intent charges might contain a mandate ID, so let's save that and make a note. + $mandate_id = $charge->payment_method_details->card->mandate; + edd_update_order_meta( $order->id, '_edds_stripe_mandate', $mandate_id ); + } + } + + // Attach the \Stripe\Customer ID to the \EDD_Customer meta if one exists. + $edd_customer = edd_get_customer_by( 'email', $this->purchase_data['user_email'] ); + if ( $edd_customer ) { + $edd_customer->update_meta( edd_stripe_get_customer_key(), $intent->customer ); + } + + if ( \EDD\Checkout\AutoRegister::is_enabled() ) { + remove_action( 'set_logged_in_cookie', 'edd_set_logged_in_cookie' ); + } + + $this->do_legacy_create_hook( $order, $intent ); + + /** + * Allows further processing after a order is created. + * + * Sends back just the Intent ID to avoid needing always retrieve + * the intent in this step, which has been transformed via JSON, + * and is no longer a \Stripe\PaymentIntent + * + * @since 2.9.0 + * + * @param \EDD\Orders\Order $order EDD Order Object. + * @param \Stripe\PaymentIntent|\Stripe\SetupIntent $intent Created Stripe Intent. + */ + do_action( 'edds_order_created', $order, $intent ); + + return $order_id; + } + + /** + * Add the payment method to the order meta. + * + * @since 3.3.5 + * @param int $order_id The order ID. + * @param mixed $payment_method_details The payment method details. + */ + public static function add_payment_method( $order_id, $payment_method_details ) { + if ( empty( $payment_method_details->type ) ) { + return; + } + $type = $payment_method_details->type; + if ( 'card' === $type && ! empty( $payment_method_details->card->wallet->type ) ) { + $type = $payment_method_details->card->wallet->type; + } + if ( $type !== edd_get_order_meta( $order_id, 'stripe_payment_method_type', true ) ) { + edd_update_order_meta( $order_id, 'stripe_payment_method_type', $type ); + } + } + + /** + * Retrieve the purchase data. + * + * @since 3.3.5 + * @return array + * @throws \EDD_Stripe_Gateway_Exception If an error occurs. + */ + private function get_purchase_data() { + $purchase_data = \EDD\Sessions\PurchaseData::get( false ); + if ( empty( $purchase_data ) ) { + throw new \EDD_Stripe_Gateway_Exception( + esc_html__( + 'Error 1004: An error occurred, but your payment may have gone through. Please contact the site administrator.', + 'easy-digital-downloads' + ), + 'Unable to retrieve purchase data during payment creation.' + ); + } + + return $purchase_data; + } + + /** + * Maybe update the Intent with the EDD order id. + * + * @since 3.3.5 + * @param mixed $intent The Intent. + * @param int $order_id The order ID. + * @param string $intent_type The Intent type. + * @return mixed The Intent. + */ + private function maybe_update_intent( $intent, $order_id, $intent_type ) { + + if ( isset( $intent->metadata->edd_payment_id ) ) { + return $intent; + } + + return edds_api_request( + $intent_type, + 'update', + $intent->id, + array( + 'metadata' => array( + 'edd_payment_id' => $order_id, + ), + ) + ); + } + + /** + * Legacy hook for `edds_payment_created`. + * + * @since 3.3.5 + */ + private function do_legacy_create_hook( $order, $intent ) { + if ( ! has_action( 'edds_payment_created' ) ) { + return; + } + + // Load up an EDD Payment record here, in the event there is something hooking into it. + $payment = new \EDD_Payment( $order->id ); + + /** + * Allows further processing after a payment is created. + * + * NOTE TO DEVELOPERS: Only hook into one of these complete hooks. Using both will result in + * unexpected double processing. + * + * @since 2.7.0 + * + * @param \EDD_Payment $payment EDD Payment. + * @param \Stripe\PaymentIntent|\Stripe\SetupIntent $intent Created Stripe Intent. + */ + do_action( 'edds_payment_created', $payment, $intent ); + } +} diff --git a/src/Gateways/Stripe/Checkout/Recurring.php b/src/Gateways/Stripe/Checkout/Recurring.php new file mode 100644 index 00000000000..51baf89cea8 --- /dev/null +++ b/src/Gateways/Stripe/Checkout/Recurring.php @@ -0,0 +1,141 @@ +is_purchase_recurring( $purchase_data ) ) { + return; + } + + $intent = Intents::get( + $intent_id, + array( + 'expand' => array( 'latest_charge' ), + ) + ); + if ( ! $intent ) { + return; + } + + self::maybe_filter_subscription_args( $intent, $order ); + + $edd_recurring_stripe->process_purchase_form( $order, $intent ); + self::activate_subscriptions( $order ); + } + + /** + * Complete the EDD subscriptions. + * + * @since 3.3.5 + * @param \EDD\Orders\Order $order The order object. + */ + public static function activate_subscriptions( $order ) { + if ( 'complete' !== $order->status ) { + return; + } + if ( ! class_exists( '\\EDD_Subscriptions_DB' ) ) { + return; + } + $subscription_db = new \EDD_Subscriptions_DB(); + $subscriptions = $subscription_db->get_subscriptions( + array( + 'parent_payment_id' => $order->id, + ) + ); + + foreach ( $subscriptions as $subscription ) { + if ( 'pending' !== $subscription->status ) { + continue; + } + $subscription->update( + array( + 'status' => empty( $subscription->trial_period ) ? 'active' : 'trialling', + ) + ); + } + } + + /** + * Maybe filter the subscription args. + * + * @since 3.3.5 + * @param \EDD\Vendor\Stripe\PaymentIntent $intent The PaymentIntent object. + * @param \EDD\Orders\Order $order The order object. + */ + private static function maybe_filter_subscription_args( $intent, $order ) { + if ( empty( $intent->latest_charge ) || ! $intent->latest_charge instanceof \EDD\Vendor\Stripe\Charge ) { + return; + } + + $custom_payment_method_id = self::get_custom_payment_method( $intent->latest_charge, $order ); + if ( ! $custom_payment_method_id ) { + return; + } + + add_filter( + 'edd_recurring_create_subscription_args', + function ( $args ) use ( $custom_payment_method_id ) { + $args['default_payment_method'] = $custom_payment_method_id; + + return $args; + } + ); + } + + /** + * Try to get the custom payment method that was attached to the customer. + * + * @since 3.3.5 + * @param \EDD\Vendor\Stripe\Charge $charge The charge object. + * @param \EDD\Orders\Order $order The order object. + * @return false|string + */ + private static function get_custom_payment_method( $charge, $order ) { + if ( empty( $charge->payment_method_details ) ) { + return false; + } + $payment_method_type = edd_get_order_meta( $order->id, 'stripe_payment_method_type', true ); + if ( in_array( $payment_method_type, array( 'card', 'link' ), true ) ) { + return false; + } + + if ( empty( $charge->payment_method_details->{$payment_method_type} ) ) { + return false; + } + + $payment_method = false; + // Try to get the custom payment method ID from the charge. + if ( ! empty( $charge->payment_method_details->{$payment_method_type}->generated_sepa_debit ) ) { + return $charge->payment_method_details->{$payment_method_type}->generated_sepa_debit; + } + + return $payment_method; + } +} diff --git a/src/Gateways/Stripe/Intents.php b/src/Gateways/Stripe/Intents.php new file mode 100644 index 00000000000..3c8c7d7bec4 --- /dev/null +++ b/src/Gateways/Stripe/Intents.php @@ -0,0 +1,68 @@ + $intent_id, + $args, + ); + } +} diff --git a/src/Gateways/Stripe/License.php b/src/Gateways/Stripe/License.php new file mode 100644 index 00000000000..98bb729d916 --- /dev/null +++ b/src/Gateways/Stripe/License.php @@ -0,0 +1,373 @@ +is_pass_valid_for_stripe(); + $this->license_data = $this->get_license_data(); + } + + /** + * Whether the license is active and valid. + * + * @since 3.2.0 + * @return bool + */ + public function is_license_valid() { + return ! empty( $this->license_data->success ) && 'valid' === $this->license_data->license; + } + + /** + * Gets the license expiration. + * + * @since 3.2.0 + * @return string|int|bool + */ + public function get_expiration() { + return ! empty( $this->license_data->expires ) ? $this->license_data->expires : false; + } + + /** + * Whether the license is expired. + * + * @since 3.2.0 + * @return bool + */ + public function is_expired() { + return $this->license_data ? $this->license_data->is_expired() : false; + } + + /** + * Whether the license is in a grace period. + * + * @since 3.2.0 + * @return bool + */ + public function is_in_grace_period() { + $expiration = $this->get_expiration(); + if ( empty( $expiration ) || 'lifetime' === $expiration ) { + return false; + } + if ( $this->is_expired() ) { + $now = current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested + $expiration = strtotime( $expiration ); + // Fourteen day grace period. + return ( $now - $expiration < ( DAY_IN_SECONDS * 14 ) ); + } + + return false; + } + + /** + * Whether the license is in a new install grace period. + * + * @since 3.2.0 + * @return bool + */ + public function is_in_new_install_grace_period() { + if ( $this->is_license_valid() ) { + return false; + } + if ( ! empty( $this->license_data->key ) ) { + return false; + } + + $end_time = $this->get_new_install_grace_period_end_time(); + if ( $end_time && time() < $end_time ) { + return true; + } + + return false; + } + + /** + * Whether the license is expiring in the next two weeks. + * + * @since 3.2.0 + * @return bool + */ + public function is_expiring_soon() { + if ( $this->is_expired() ) { + return false; + } + $expiration = $this->get_expiration(); + if ( ! $expiration || 'lifetime' === $expiration ) { + return false; + } + if ( strtotime( $expiration ) - time() <= ( DAY_IN_SECONDS * 14 ) ) { + return true; + } + + return false; + } + + /** + * Gets the localized date for the end of the grace period. + * + * @since 3.2.0 + * @return string + */ + public function get_grace_period_end_date() { + if ( ! $this->is_in_grace_period() ) { + return ''; + } + + return $this->get_localized_date( strtotime( $this->get_expiration() ) + ( DAY_IN_SECONDS * 14 ) ); + } + + /** + * Gets the localized date for the expiration date. + * + * @since 3.2.0 + * @return string + */ + public function get_expiration_date() { + $expiration = $this->get_expiration(); + if ( empty( $expiration ) || 'lifetime' === $expiration ) { + return ''; + } + + return $this->get_localized_date( strtotime( $expiration ) ); + } + + /** + * Gets the localized date for the end of the new install grace period. + * + * @since 3.2.0 + * @return string + */ + public function get_new_install_grace_period_end_date() { + $end_time = $this->get_new_install_grace_period_end_time(); + + return $end_time ? $this->get_localized_date( $end_time ) : ''; + } + + /** + * Gets the renewal URL. + * + * @since 3.2.0 + * @param string $license_status The license status. + * @return string + */ + public function get_renewal_url( $license_status ) { + $args = array( + 'utm_medium' => 'license-notice', + 'utm_content' => $license_status, + ); + if ( ! empty( $this->license_data->key ) ) { + $args['license_key'] = $this->license_data->key; + } + + return edd_link_helper( + 'https://easydigitaldownloads.com/checkout/', + $args + ); + } + + /** + * Gets the URL for the licenses tab. + * + * @since 3.2.0 + * @return string + */ + public function get_licensing_url() { + $args = array( + 'page' => 'edd-settings', + ); + if ( ( function_exists( 'edd_is_admin_page' ) && edd_is_admin_page( 'settings', 'general' ) ) || ( ! edd_is_pro() && edds_is_pro() ) ) { + $args['tab'] = 'licenses'; + } + + return edd_get_admin_url( $args ); + } + + /** + * Gets the license data. + * + * @since 3.2.0 + * @return \EDD\Licensing\License|false + */ + private function get_license_data() { + + // Check for a pass license first. + $pass_license = $this->get_pass_license(); + if ( $pass_license ) { + $this->is_pass_license = true; + + return $pass_license; + } + + // Check for a Stripe Pro license key. + $license = new \EDD\Licensing\License( $this->item_name ); + if ( ! empty( $license->key ) && ! empty( $license->item_id ) ) { + return $license; + } + + if ( $this->is_pass_valid_for_stripe() ) { + return $this->maybe_fix_missing_license(); + } + + return false; + } + + /** + * Gets the pass license. + * + * @since 3.2.0 + * @return \EDD\Licensing\License|false + */ + private function get_pass_license() { + $pro_license = new \EDD\Licensing\License( 'pro' ); + if ( empty( $pro_license->key ) ) { + return false; + } + if ( ! empty( $pro_license->key ) && $this->is_pass_valid_for_stripe() ) { + return $pro_license; + } + + return false; + } + + /** + * Whether the pass is valid for Stripe Pro, even if it's not a pro license. + * + * @since 3.2.1 + * @return bool + */ + private function is_pass_valid_for_stripe() { + if ( ! is_null( $this->is_pass_valid_for_stripe ) ) { + return $this->is_pass_valid_for_stripe; + } + $this->is_pass_valid_for_stripe = false; + $pass_manager = new \EDD\Admin\Pass_Manager(); + if ( empty( $pass_manager->highest_pass_id ) ) { + return $this->is_pass_valid_for_stripe; + } + + $this->pass_id = $pass_manager->highest_pass_id; + $this->is_pass_valid_for_stripe = $pass_manager::pass_compare( $pass_manager->highest_pass_id, $pass_manager::EXTENDED_PASS_ID, '>=' ); + + return $this->is_pass_valid_for_stripe; + } + + /** + * Gets the end time for the new install grace period. + * + * @since 3.2.0 + * @return int|bool + */ + private function get_new_install_grace_period_end_time() { + $installed = get_transient( 'edd_stripe_new_install' ); + + return $installed ? $installed + ( HOUR_IN_SECONDS * 72 ) : false; + } + + /** + * Gets the localized date for the given timestamp. + * + * @since 3.2.0 + * @param int $date The timestamp to localize. + * @return string + */ + private function get_localized_date( $date ) { + $format = get_option( 'date_format', 'Y-m-d' ); + if ( ! is_numeric( $date ) ) { + $date = strtotime( $date ); + } + + return date( // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date + $format, + $date + ); + } + + /** + * Fixes a missing Stripe Pro license. + * + * @since 3.2.1 + * @return bool|\EDD\Licensing\License + */ + private function maybe_fix_missing_license() { + if ( ! empty( $this->license_data ) ) { + return false; + } + if ( edd_is_pro() ) { + return false; + } + + $pass_manager = new \EDD\Admin\Pass_Manager(); + $shortname = 'edd_' . preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $this->item_name ) ) ); + + // Save the license key for Stripe. + edd_update_option( "{$shortname}_license_key", $pass_manager->highest_license_key ); + + $api_params = array( + 'edd_action' => 'activate_license', + 'license' => $pass_manager->highest_license_key, + 'item_name' => $this->item_name, + 'item_id' => 167, + 'environment' => wp_get_environment_type(), + ); + $api = new \EDD\Licensing\API(); + $license_data = $api->make_request( $api_params ); + + // Save the license data, no matter what the response was. + $license = new \EDD\Licensing\License( $this->item_name ); + $license->save( $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' ); + } + + // Return a new license object. + return new \EDD\Licensing\License( $this->item_name ); + } +} diff --git a/src/Gateways/Stripe/PaymentMethods.php b/src/Gateways/Stripe/PaymentMethods.php new file mode 100644 index 00000000000..2ee31feb4d0 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods.php @@ -0,0 +1,282 @@ +get(); + if ( $value ) { + return $value; + } + + $pmc = self::get_pmc(); + if ( ! $pmc ) { + return false; + } + + $configurations->set( $pmc ); + + return $pmc; + } + + /** + * Retrieves a specific payment method configuration ID. + * + * @since 3.3.5 + * @param string $type The type of payment method configuration to retrieve. + * @return string The configuration ID. + */ + public static function get_configuration_id( $type = '' ) { + $base = 'edd20241002'; + $configurations = self::get_configurations(); + if ( empty( $configurations ) || ! is_array( $configurations ) || ! array_key_exists( $base, $configurations ) ) { + return false; + } + $configuration = $base; + if ( $type ) { + $configuration = "{$base}-{$type}"; + } + + return array_key_exists( $configuration, $configurations ) ? $configurations[ $configuration ] : false; + } + + /** + * Retrieves the base configuration for the payment methods. + * + * @since 3.3.5 + * @return array The array of payment methods in the base configuration. + */ + public static function get_base_configuration() { + $configuration_id = self::get_configuration_id(); + if ( ! $configuration_id ) { + return false; + } + if ( ! edd_stripe()->connect->is_connected ) { + return false; + } + + $configuration = new \EDD\Utils\Transient( $configuration_id ); + $value = $configuration->get(); + if ( $value ) { + return $value; + } + + try { + $value = edds_api_request( + 'PaymentMethodConfiguration', + 'retrieve', + $configuration_id + ); + } catch ( \Exception $e ) { + return false; + } + + $methods = array(); + foreach ( self::list() as $method => $label ) { + if ( ! isset( $value->$method ) ) { + continue; + } + $methods[ $method ] = $value->$method; + } + + $configuration->set( $methods ); + + return $methods; + } + + /** + * Checks if the Affirm payment method is supported. + * + * @since 3.3.5 + * @return bool True if the Affirm payment method is supported, false otherwise. + */ + public static function affirm_requires_support() { + if ( 'payment-elements' !== edds_get_elements_mode() ) { + return false; + } + + $affirm = self::get_payment_method( 'affirm' ); + if ( ! $affirm ) { + return false; + } + + if ( ! in_array( edd_get_currency(), $affirm::$currencies, true ) ) { + return false; + } + + $display = edd_get_option( 'stripe_billing_fields', 'full' ); + if ( ! in_array( $display, array( 'full', 'zip_country' ), true ) ) { + return false; + } + + if ( edd_get_cart_total() < 50 ) { + return false; + } + + if ( function_exists( 'edd_recurring' ) && edd_recurring()->cart_contains_recurring() ) { + return false; + } + + $payment_configuration = self::get_base_configuration(); + + return $payment_configuration && ! empty( $payment_configuration['affirm']['available'] ); + } + + /** + * Retrieves the supported payment methods. + * + * @since 3.3.5 + * @return array The supported payment methods. + */ + private static function get_registered_methods() { + return array( + 'acss_debit', + 'ach_debit', + 'affirm', + 'alipay', + 'amazon_pay', + 'apple_pay', + 'bacs_debit', + 'bancontact', + 'card', + 'cartes_bancaires', + 'cashapp', + 'eps', + 'fpx', + 'giropay', + 'google_pay', + 'grabpay', + 'ideal', + 'link', + 'p24', + 'revolut_pay', + 'sepa_debit', + 'sofort', + 'twint', + 'us_bank_account', + 'wechat_pay', + ); + } + + /** + * Retrieves the class name for the specified payment method. + * + * @since 3.3.5 + * @param string $method The payment method. + * @return string|false The class name for the specified payment method, or false if not found. + */ + public static function get_payment_method( $method ) { + $method = str_replace( '_', ' ', $method ); + $method = ucwords( $method ); + $method = str_replace( ' ', '', $method ); + $classname = __NAMESPACE__ . '\\PaymentMethods\\' . $method; + + return class_exists( $classname ) ? $classname : false; + } + + /** + * Resets the payment method configuration options. + * + * @since 3.3.5 + */ + public static function reset() { + delete_option( 'edd_stripe_account_capabilities' ); + foreach ( array( 'test', 'live' ) as $mode ) { + $option = get_option( "edd_stripe_pmc_{$mode}" ); + if ( $option ) { + $configurations = json_decode( $option, true ); + if ( ! empty( $configurations['value']['edd20241002'] ) ) { + delete_option( $configurations['value']['edd20241002'] ); + } + delete_option( "edd_stripe_pmc_{$mode}" ); + } + } + } + + /** + * Retrieves the payment method configuration for EDD. + * + * @since 3.3.5 + * @return object The configuration for the specified payment method. + */ + private static function get_pmc() { + if ( ! edd_stripe()->connect || ! edd_stripe()->connect->is_connected ) { + return false; + } + + try { + $pmc = edds_api_request( + 'PaymentMethodConfiguration', + 'all', + array( + 'application' => 'ca_CCnYEUzwAy5xzFgSVNc8C5jw7Zagm5cH', + ) + ); + } catch ( \Exception $e ) { + edd_record_gateway_error( 'stripe', 'edds_api_error', $e->getMessage() ); + + return false; + } + + $configurations = array(); + foreach ( $pmc->data as $configuration ) { + $configurations[ strtolower( $configuration->name ) ] = $configuration->id; + } + + return $configurations; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/AchDebit.php b/src/Gateways/Stripe/PaymentMethods/AchDebit.php new file mode 100644 index 00000000000..f3f85a59904 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/AchDebit.php @@ -0,0 +1,62 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/AcssDebit.php b/src/Gateways/Stripe/PaymentMethods/AcssDebit.php new file mode 100644 index 00000000000..056cac5c7f7 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/AcssDebit.php @@ -0,0 +1,54 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Affirm.php b/src/Gateways/Stripe/PaymentMethods/Affirm.php new file mode 100644 index 00000000000..96b9e636f08 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Affirm.php @@ -0,0 +1,62 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Alipay.php b/src/Gateways/Stripe/PaymentMethods/Alipay.php new file mode 100644 index 00000000000..935c7e3f644 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Alipay.php @@ -0,0 +1,101 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/AmazonPay.php b/src/Gateways/Stripe/PaymentMethods/AmazonPay.php new file mode 100644 index 00000000000..c57ca306096 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/AmazonPay.php @@ -0,0 +1,78 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/ApplePay.php b/src/Gateways/Stripe/PaymentMethods/ApplePay.php new file mode 100644 index 00000000000..46ba374091c --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/ApplePay.php @@ -0,0 +1,70 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/BacsDebit.php b/src/Gateways/Stripe/PaymentMethods/BacsDebit.php new file mode 100644 index 00000000000..5193f0829e0 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/BacsDebit.php @@ -0,0 +1,62 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Bancontact.php b/src/Gateways/Stripe/PaymentMethods/Bancontact.php new file mode 100644 index 00000000000..2dc8e74330b --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Bancontact.php @@ -0,0 +1,110 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Card.php b/src/Gateways/Stripe/PaymentMethods/Card.php new file mode 100644 index 00000000000..0e7c5f0fcd1 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Card.php @@ -0,0 +1,70 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/CartesBancaires.php b/src/Gateways/Stripe/PaymentMethods/CartesBancaires.php new file mode 100644 index 00000000000..1f33e06a32a --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/CartesBancaires.php @@ -0,0 +1,62 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Cashapp.php b/src/Gateways/Stripe/PaymentMethods/Cashapp.php new file mode 100644 index 00000000000..12aa862585e --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Cashapp.php @@ -0,0 +1,86 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Eps.php b/src/Gateways/Stripe/PaymentMethods/Eps.php new file mode 100644 index 00000000000..36c5dee3bab --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Eps.php @@ -0,0 +1,54 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Fpx.php b/src/Gateways/Stripe/PaymentMethods/Fpx.php new file mode 100644 index 00000000000..c3d75900a4e --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Fpx.php @@ -0,0 +1,62 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Giropay.php b/src/Gateways/Stripe/PaymentMethods/Giropay.php new file mode 100644 index 00000000000..eb68ec1f017 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Giropay.php @@ -0,0 +1,54 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/GooglePay.php b/src/Gateways/Stripe/PaymentMethods/GooglePay.php new file mode 100644 index 00000000000..2fbdbdb8550 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/GooglePay.php @@ -0,0 +1,70 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Grabpay.php b/src/Gateways/Stripe/PaymentMethods/Grabpay.php new file mode 100644 index 00000000000..8402cfda4b0 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Grabpay.php @@ -0,0 +1,65 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Ideal.php b/src/Gateways/Stripe/PaymentMethods/Ideal.php new file mode 100644 index 00000000000..18f35daa320 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Ideal.php @@ -0,0 +1,111 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Link.php b/src/Gateways/Stripe/PaymentMethods/Link.php new file mode 100644 index 00000000000..2c679535b85 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Link.php @@ -0,0 +1,70 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Method.php b/src/Gateways/Stripe/PaymentMethods/Method.php new file mode 100644 index 00000000000..f95d1341bb6 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Method.php @@ -0,0 +1,84 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/P24.php b/src/Gateways/Stripe/PaymentMethods/P24.php new file mode 100644 index 00000000000..aa4efdc3b3a --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/P24.php @@ -0,0 +1,102 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/RevolutPay.php b/src/Gateways/Stripe/PaymentMethods/RevolutPay.php new file mode 100644 index 00000000000..d64c19bb6c0 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/RevolutPay.php @@ -0,0 +1,70 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/SepaDebit.php b/src/Gateways/Stripe/PaymentMethods/SepaDebit.php new file mode 100644 index 00000000000..a4e9bea9c1b --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/SepaDebit.php @@ -0,0 +1,103 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Sofort.php b/src/Gateways/Stripe/PaymentMethods/Sofort.php new file mode 100644 index 00000000000..66700d1d7f1 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Sofort.php @@ -0,0 +1,54 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/Twint.php b/src/Gateways/Stripe/PaymentMethods/Twint.php new file mode 100644 index 00000000000..c9695925230 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/Twint.php @@ -0,0 +1,64 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/UsBankAccount.php b/src/Gateways/Stripe/PaymentMethods/UsBankAccount.php new file mode 100644 index 00000000000..ef77ff8aff9 --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/UsBankAccount.php @@ -0,0 +1,54 @@ +'; + } +} diff --git a/src/Gateways/Stripe/PaymentMethods/WechatPay.php b/src/Gateways/Stripe/PaymentMethods/WechatPay.php new file mode 100644 index 00000000000..71a2e1453ee --- /dev/null +++ b/src/Gateways/Stripe/PaymentMethods/WechatPay.php @@ -0,0 +1,46 @@ +'; + } +} diff --git a/src/Gateways/Stripe/StatementDescriptor.php b/src/Gateways/Stripe/StatementDescriptor.php new file mode 100644 index 00000000000..3c9ff9a3caa --- /dev/null +++ b/src/Gateways/Stripe/StatementDescriptor.php @@ -0,0 +1,204 @@ + \ ' " * + * + * @var array + */ + public static $unsupported_characters = array( '<', '>', '\\', '\'', '"', '*' ); + + /** + * Get the statement descriptor suffix. + * + * @since 3.2.8 + * + * @param string $suffix The suffix to sanitize. + * + * @return string + */ + public static function sanitize_suffix( $suffix = '' ) { + if ( ! self::include_purchase_summary() ) { + return ''; + } + + if ( empty( $suffix ) ) { + return ''; + } + + // Determine if we need to include a latin character in the suffix. + if ( self::needs_latin_character( $suffix ) ) { + // Get a latin character to use in the suffix. + $latin_char = self::get_latin_character(); + + // Add the latin character to the suffix. + $suffix = $latin_char . $suffix; + } + + // Remove unsupported characters. + $suffix = self::remove_unsupported_characters( $suffix ); + + // The combination of prefix and suffix is longer than max length, truncate the suffix. + $length = self::prefix_length() + strlen( $suffix ); + if ( $length > self::$max_length ) { + $suffix = substr( $suffix, 0, self::$max_length - self::prefix_length() ); + } + + return strtoupper( $suffix ); + } + + /** + * Get the prefix for the statement descriptor. + * + * This is in the Stripe account settings for card_payments, and we store it for performance. + * Visiting the Stripe settings page in EDD will update this value, if it's changed in Stripe. + * + * @since 3.2.8 + * + * @return string + */ + private static function get_prefix() { + // If we already have a prefix saved, just return it. + if ( ! empty( edd_get_option( 'stripe_statement_descriptor_prefix', '' ) ) ) { + return edd_get_option( 'stripe_statement_descriptor_prefix' ); + } + + $prefix = ''; + + // There isn't a prefix saved already, get one and save it. + try { + $account_id = edd_stripe()->connect()->get_connect_id(); + + if ( ! empty( $account_id ) ) { + + $account = edds_api_request( 'Account', 'retrieve', $account_id ); + + if ( ! is_wp_error( $account ) ) { + $prefix = $account->settings->card_payments->statement_descriptor_prefix ?? ''; + } + + // If we got a prefix, save it so we don't have to look it up again. + edd_update_option( 'stripe_statement_descriptor_prefix', $prefix ); + } + } catch ( \Exception $e ) { + // do nothing. + } + + return $prefix; + } + + /** + * Determine if we need to include a latin character in the suffix. + * + * Stripe has the requirement of a combination of a prefix and suffix contains at least one latin character. + * + * @since 3.2.8 + * + * @param string $suffix The suffix to check. + * + * @return bool + */ + private static function needs_latin_character( $suffix ) { + // If the suffix already has a latin character, we don't need to add one. + if ( preg_match( '/[a-zA-Z]/', $suffix ) ) { + return false; + } + + // If the prefix already has a latin character, we don't need to add one. + if ( preg_match( '/[a-zA-Z]/', self::get_prefix() ) ) { + return false; + } + + return true; + } + + /** + * Get a latin character to use in our suffix. + * + * We'll use the first latin character in the prefix to ensure that the statement descriptor is valid. + * If it doesn't contain one, we'll just use 'E', for EDD. We'll avoid the letter 'O' by default here as it could be confused + * with the number 0 depending on how the statement descriptor is displayed at the financial institution. + * + * @since 3.2.8 + * + * @return string + */ + private static function get_latin_character() { + // Get the first character of the prefix. + $latin_character = substr( self::get_prefix(), 0, 1 ); + if ( ! preg_match( '/[a-zA-Z]/', $latin_character ) ) { + $latin_character = 'E'; + } + + return strtoupper( $latin_character ) . '-'; + } + + /** + * Get the length of the prefix. + * + * Stripe always adds a * and a space at the end of the prefix, so we need to account for that. + * + * @since 3.2.8 + * + * @return int + */ + private static function prefix_length() { + return strlen( self::get_prefix() . '* ' ); + } + + /** + * Remove any unsupported characters from the suffix. + * + * @since 3.2.8 + * + * @param string $suffix The suffix to sanitize. + * + * @return string + */ + private static function remove_unsupported_characters( $suffix ) { + // Remove any characters that are not supported by Stripe. + $suffix = str_replace( self::$unsupported_characters, '', $suffix ); + + // Remove any spaces in the suffix. + $suffix = str_replace( ' ', '', $suffix ); + + return $suffix; + } + + /** + * Whether to include the purchase summary in the payment descriptor. + * + * @since 3.2.8 + * + * @return bool + */ + public static function include_purchase_summary() { + return (bool) edd_get_option( 'stripe_include_purchase_summary_in_statement_descriptor', false ); + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/ChargeDisputeCreated.php b/src/Gateways/Stripe/Webhooks/Events/ChargeDisputeCreated.php new file mode 100644 index 00000000000..fb488302654 --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/ChargeDisputeCreated.php @@ -0,0 +1,44 @@ +object->charge ); + if ( $order_id ) { + edd_record_order_dispute( $order_id, $this->object->charge, $this->object->reason ); + do_action( 'edd_stripe_dispute_created', $this->object, $order_id ); + } + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/ChargeFailed.php b/src/Gateways/Stripe/Webhooks/Events/ChargeFailed.php new file mode 100644 index 00000000000..83045b8352d --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/ChargeFailed.php @@ -0,0 +1,40 @@ +get_order(); + if ( ! $order instanceof \EDD\Orders\Order ) { + return; + } + + if ( ! empty( $this->object->payment_method_details ) ) { + \EDD\Gateways\Stripe\Checkout\Order::add_payment_method( $order->id, $this->object->payment_method_details ); + } + + edd_update_order_status( $order->id, 'failed' ); + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/ChargeRefunded.php b/src/Gateways/Stripe/Webhooks/Events/ChargeRefunded.php new file mode 100644 index 00000000000..ac99cbea095 --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/ChargeRefunded.php @@ -0,0 +1,83 @@ +object->captured ) { + return; + } + + $order = $this->get_order(); + if ( ! $order instanceof \EDD\Orders\Order ) { + return; + } + + // If this was completely refunded, set the status to refunded. + if ( $this->object->refunded ) { + $refund_id = $this->get_refund_id( $order ); + + if ( $refund_id && ! is_wp_error( $refund_id ) ) { + edd_add_order_transaction( + array( + 'object_type' => 'order', + 'object_id' => $refund_id, + 'transaction_id' => $this->object->id, + 'gateway' => 'stripe', + 'total' => $order->total, + 'status' => 'complete', + 'currency' => $order->currency, + ) + ); + } else { + edd_update_order_status( $order->id, 'refunded' ); + } + /* translators: The charge ID from Stripe that is being refunded. */ + $note = sprintf( __( 'Charge %s has been fully refunded in Stripe.', 'easy-digital-downloads' ), $this->object->id ); + } else { + edd_update_order_status( $order->id, 'partially_refunded' ); + /* translators: The charge ID from Stripe that is being partially refunded. */ + $note = sprintf( __( 'Charge %s partially refunded in Stripe.', 'easy-digital-downloads' ), $this->object->id ); + } + edd_add_note( + array( + 'object_id' => $order->id, + 'object_type' => 'order', + 'content' => $note, + ) + ); + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/ChargeSucceeded.php b/src/Gateways/Stripe/Webhooks/Events/ChargeSucceeded.php new file mode 100644 index 00000000000..3bfdb3e55bc --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/ChargeSucceeded.php @@ -0,0 +1,86 @@ +get_order(); + if ( ! $order instanceof \EDD\Orders\Order ) { + return; + } + + $customer = edd_get_customer( $order->customer_id ); + $address = array( + 'order_id' => $order->id, + 'name' => $customer->name, + 'address' => $this->object->billing_details->address->line1, + 'address2' => $this->object->billing_details->address->line2, + 'region' => $this->object->billing_details->address->state, + 'city' => $this->object->billing_details->address->city, + 'postal_code' => $this->object->billing_details->address->postal_code, + 'country' => $this->object->billing_details->address->country, + ); + + edd_add_order_address( $address ); + + if ( ! empty( $this->object->payment_method_details ) ) { + \EDD\Gateways\Stripe\Checkout\Order::add_payment_method( $order->id, $this->object->payment_method_details ); + } + + // If the order is pending and has no parent, mark it as complete. This catches slower payment methods like bank transfers. + if ( $this->should_mark_complete( $order ) ) { + \EDD\Gateways\Stripe\Checkout\Complete::mark_complete_from_charge( $order, $this->object ); + } + } + + /** + * Determine if the order should be marked as complete from the charge. + * + * @since 3.3.5 + * @param \EDD\Orders\Order $order The order object. + * @return bool + */ + private function should_mark_complete( $order ) { + if ( 'pending' !== $order->status ) { + return false; + } + + if ( ! empty( $order->parent ) ) { + return false; + } + + return in_array( edd_get_order_meta( $order->id, 'stripe_payment_method_type', true ), array( 'us_bank_account', 'sofort' ), true ); + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/Event.php b/src/Gateways/Stripe/Webhooks/Events/Event.php new file mode 100644 index 00000000000..9fae5be4e7a --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/Event.php @@ -0,0 +1,80 @@ +event = $event; + $this->data = $event->data; + $this->object = $event->data->object; + } + + /** + * Check the event mode against the store mode. + * + * @since 3.3.0 + * @return bool + */ + abstract public function process(); + + /** + * Check if the requirements are met for processing the event. + * + * @since 3.3.0 + * @return bool + */ + public function requirements_met() { + return true; + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/RadarEarlyFraudWarningCreated.php b/src/Gateways/Stripe/Webhooks/Events/RadarEarlyFraudWarningCreated.php new file mode 100644 index 00000000000..bc18c0bd703 --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/RadarEarlyFraudWarningCreated.php @@ -0,0 +1,73 @@ +get_order( $this->object->charge ); + + if ( $order instanceof \EDD\Orders\Order ) { + // Add an order note. + edd_add_note( + array( + 'object_id' => $order->id, + 'object_type' => 'order', + 'content' => sprintf( + /* translators: %s Stripe Radar early fraud warning reason. */ + __( 'Stripe Radar early fraud warning created with a reason of %s.', 'easy-digital-downloads' ), + $this->object->fraud_type + ), + ) + ); + + do_action( 'edd_stripe_early_fraud_warning', $order, $this->object ); + } + } + + /** + * Check if the requirements for this event are met. + * + * The early fraud warning event requires that the store be using the Payment Elements integration. The legacy + * Card Elements integration can hit a race condition where the early fraud warning event is triggered before EDD has captured + * and created the EDD transaction. + * + * @since 3.3.0 + * + * @return bool + */ + public function requirements_met() { + return 'payment-elements' === edds_get_elements_mode(); + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/ReviewClosed.php b/src/Gateways/Stripe/Webhooks/Events/ReviewClosed.php new file mode 100644 index 00000000000..667a8e8d468 --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/ReviewClosed.php @@ -0,0 +1,70 @@ +object->charge; + + // Get the charge from the PaymentIntent. + if ( ! $charge ) { + $payment_intent = $this->object->payment_intent; + + if ( ! $payment_intent ) { + return; + } + + $payment_intent = edds_api_request( 'PaymentIntent', 'retrieve', $payment_intent ); + $charge = $payment_intent->charges->data[0]->id; + } + + $order = $this->get_order( $charge ); + if ( $order instanceof \EDD\Orders\Order ) { + edd_add_note( + array( + 'object_id' => $order->id, + 'object_type' => 'order', + 'content' => sprintf( + /* translators: %s Stripe Radar review closing reason. */ + __( 'Stripe Radar review closed with a reason of %s.', 'easy-digital-downloads' ), + $this->object->reason + ), + ) + ); + + do_action( 'edd_stripe_review_closed', $this->object, $order->id ); + } + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/ReviewOpened.php b/src/Gateways/Stripe/Webhooks/Events/ReviewOpened.php new file mode 100644 index 00000000000..67c8599e9d1 --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/ReviewOpened.php @@ -0,0 +1,77 @@ +object->charge; + + // Get the charge from the PaymentIntent. + if ( ! $charge ) { + $payment_intent = $this->object->payment_intent; + + if ( ! $payment_intent ) { + return; + } + + try { + $payment_intent = edds_api_request( 'PaymentIntent', 'retrieve', $payment_intent ); + $charge = $payment_intent->charges->data[0]->id; + } catch ( EDD_Exception $exception ) { + throw $exception; + } + } + + $order = $this->get_order( $charge ); + if ( $order instanceof \EDD\Orders\Order ) { + edd_add_note( + array( + 'object_id' => $order->id, + 'object_type' => 'order', + 'content' => sprintf( + /* translators: %s Stripe Radar review opening reason. */ + __( 'Stripe Radar review opened with a reason of %s.', 'easy-digital-downloads' ), + $this->object->reason + ), + ) + ); + + do_action( 'edd_stripe_review_opened', $this->object, $order->id ); + } + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/Traits/Mode.php b/src/Gateways/Stripe/Webhooks/Events/Traits/Mode.php new file mode 100644 index 00000000000..bb8ef3ba008 --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/Traits/Mode.php @@ -0,0 +1,33 @@ +event->livemode ? 'live' : 'test'; + + return $store_mode === $event_mode; + } +} diff --git a/src/Gateways/Stripe/Webhooks/Events/Traits/Order.php b/src/Gateways/Stripe/Webhooks/Events/Traits/Order.php new file mode 100644 index 00000000000..1f7795ef9bb --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Events/Traits/Order.php @@ -0,0 +1,76 @@ +object->id; + } + $order_id = edd_get_order_id_from_transaction_id( $transaction_id ); + if ( $order_id ) { + return edd_get_order( $order_id ); + } + + // If the order transaction wasn't updated with the final transaction ID, update it now. + $transaction = edd_get_order_transaction_by( 'transaction_id', $this->object->payment_intent ); + if ( $transaction ) { + $order_id = $transaction->object_id; + edd_update_order_transaction( + $transaction->id, + array( + 'transaction_id' => $this->object->id, + 'status' => 'complete', + ) + ); + + return edd_get_order( $order_id ); + } + + return false; + } + + /** + * Get a refund ID for an order. + * + * This method will either create a refund and return it's ID or return the ID of an existing refund. + * + * @param \EDD\Orders\Order $order The order object. + * @return int|bool The refund ID or false if no refund was created. + */ + private function get_refund_id( $order ) { + if ( edd_is_order_refundable( $order->id ) ) { + return edd_refund_order( $order->id ); + } + + $refunds = edd_get_orders( + array( + 'type' => 'refund', + 'parent' => $order->id, + ) + ); + + if ( ! empty( $refunds ) ) { + return $refunds[0]->id; + } + + return false; + } +} diff --git a/src/Gateways/Stripe/Webhooks/Listener.php b/src/Gateways/Stripe/Webhooks/Listener.php new file mode 100644 index 00000000000..3cea3585c1b --- /dev/null +++ b/src/Gateways/Stripe/Webhooks/Listener.php @@ -0,0 +1,180 @@ + 'process_webhook', + ); + } + + /** + * Processes the webhook. + * + * @since 3.3.0 + * @return void + */ + public function process_webhook() { + if ( ! $this->should_capture_webhook() ) { + return; + } + + try { + $this->retrieve_event(); + + $event_type = $this->event->type; + $event_class = $this->parse_event_class( $event_type ); + + /** + * We typically try and use the ::class constant, but we need to be able to support + * events that are not registered classes, so we will manually build the class name, + * so we don't trigger the catch block, which allows a hook to fire for the event. + */ + $event_class = __NAMESPACE__ . '\\Events\\' . $event_class; + + // Check our event classes to see if it exists. + if ( class_exists( $event_class ) && is_subclass_of( $event_class, 'EDD\Gateways\Stripe\Webhooks\Events\Event' ) ) { + $event = new $event_class( $this->event ); + if ( $event->verify_mode() && $event->requirements_met() ) { + $event->process(); + } + } + + /** + * Fires after the Stripe event has been processed. + * + * This is a backwards compatibility hook, meant to allow extending the webhook handling. + * + * @param string $event_type The event type. + * @parma \Stripe\Event $event The Stripe event. + */ + do_action( 'edds_stripe_event_' . $this->event->type, $this->event ); + + $this->send_success(); + + } catch ( EDD_Exception $e ) { + $this->send_failure(); + } + } + + /** + * Determines if the webhook should be captured. + * + * @since 3.3.0 + * @return bool + */ + private function should_capture_webhook() { + return isset( $_GET['edd-listener'] ) && 'stripe' === $_GET['edd-listener']; + } + + /** + * Retrieves the event. + * + * @since 3.3.0 + * @throws EDD_Exception If the event cannot be retrieved. + * @throws Utils\Exception If the event is invalid. + * @return void + */ + private function retrieve_event() { + $body = @file_get_contents( 'php://input' ); + try { + $event = json_decode( $body ); + + if ( false === $event || ! isset( $event->id ) ) { + throw new Utils\Exception( esc_html__( 'Invalid Event', 'easy-digital-downloads' ) ); + } + + $event = edds_api_request( 'Event', 'retrieve', $event->id ); + } catch ( EDD_Exception $exception ) { + throw $exception; + } + + $this->event = $event; + } + + /** + * Gets the event class. + * + * Events are in the format of `object.action` or `object.subobject.action`. We will convert these + * to camel case and remove periods to get the class name. + * + * For example: + * `charge.succeeded` would become `ChargeSucceeded`. + * `radar.early_fraud_warning.created` would become `RadarEarlyFraudWarningCreated`. + * + * @since 3.3.0 + * @param string $event_type The event type. + * @return string + */ + private function parse_event_class( $event_type ) { + // Replace the . and _ characters with spaces. + $event_type = str_replace( array( '.', '_' ), ' ', $event_type ); + + // Uppercase the first letter of each word. + $event_type = ucwords( $event_type ); + + // Remove spaces. + return str_replace( ' ', '', $event_type ); + } + + /** + * Sends a success response. + * + * @since 3.3.0 + * @return void + */ + private function send_success() { + // Nothing failed, mark complete. + status_header( 200 ); + die( esc_html( 'EDD Stripe: ' . $this->event->type ) ); + } + + /** + * Sends a failure response. + * + * This allows us to be able to have Stripe retry the webhook. + * + * @since 3.3.0 + * @return void + */ + private function send_failure() { + http_response_code( 500 ); + die( '-2' ); + exit; + } +} diff --git a/src/Globals/Loader.php b/src/Globals/Loader.php new file mode 100644 index 00000000000..716698a07ba --- /dev/null +++ b/src/Globals/Loader.php @@ -0,0 +1,45 @@ + 'include_polyfills', // We use plugins_loaded as this is run before pluggable functions are loaded. + ); + } + + /** + * Include the WordPress polyfills file. + * + * @since 3.2.0 + */ + public function include_polyfills() { + $directory = __DIR__ . '/Polyfills'; + require_once $directory . '/WordPress.php'; + if ( 'auto' === edd_get_option( 'logged_in_only' ) ) { + require_once $directory . '/AutoRegister.php'; + } + } +} diff --git a/src/Globals/Polyfills/AutoRegister.php b/src/Globals/Polyfills/AutoRegister.php new file mode 100644 index 00000000000..cf5e587f05d --- /dev/null +++ b/src/Globals/Polyfills/AutoRegister.php @@ -0,0 +1,25 @@ + $value ) { + if ( substr( $name, 0, 5 ) === 'HTTP_' ) { + $headers[ str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', substr( $name, 5 ) ) ) ) ) ] = $value; + } + } + return $headers; + } +} diff --git a/src/Globals/Polyfills/WordPress.php b/src/Globals/Polyfills/WordPress.php new file mode 100644 index 00000000000..55855ee888a --- /dev/null +++ b/src/Globals/Polyfills/WordPress.php @@ -0,0 +1,34 @@ +args = $this->parse_args( $args ); + } + + /** + * Gets the HTML for the element. + * + * @since 3.2.8 + * @return string Element HTML. + */ + abstract public function get(); + + /** + * Gets the default arguments for the element. + * + * @since 3.2.8 + * @return array + */ + abstract protected function defaults(); + + /** + * Echoes the HTML for the element. + * + * @since 3.2.8 + * @return void + */ + public function output() { + echo $this->get(); + } + + /** + * Get data elements + * + * @since 3.2.8 + * @return string + */ + protected function get_data_elements() { + if ( empty( $this->args['data'] ) ) { + return ''; + } + + $data_elements = array(); + foreach ( $this->args['data'] as $key => $value ) { + $data_elements[] = 'data-' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"'; + } + if ( empty( $this->args['data']['placeholder'] ) && ! empty( $this->args['placeholder'] ) ) { + $data_elements[] = 'data-placeholder="' . esc_attr( $this->args['placeholder'] ) . '"'; + } + + return implode( ' ', $data_elements ); + } + + /** + * Gets the CSS class string for the element. + * + * @since 3.2.8 + * @param array $classes The classes to add to the element. + * @return string + */ + final protected function get_css_class_string( array $classes = array() ): string { + $classes = array_merge( $this->get_base_classes(), $classes, $this->get_css_classes_from_args() ); + + return $this->array_to_css_string( $classes ); + } + + /** + * Gets a CSS class string from any array. + * + * @since 3.2.8 + * @param array $classes The array of classes. + * @return string + */ + final protected function array_to_css_string( $classes ) { + return implode( ' ', array_map( 'sanitize_html_class', array_unique( array_filter( $classes ) ) ) ); + } + + /** + * Gets the CSS classes from the arguments. + * + * @since 3.2.8 + * @return array + */ + private function get_css_classes_from_args() { + if ( empty( $this->args['class'] ) ) { + return array(); + } + $custom_classes = $this->args['class']; + if ( ! is_array( $custom_classes ) ) { + $custom_classes = explode( ' ', $custom_classes ); + } + + return array_map( 'sanitize_html_class', array_filter( $custom_classes ) ); + } + + /** + * Gets the base classes for an element which cannot be overwritten. + * + * @since 3.2.8 + * @return array + */ + protected function get_base_classes(): array { + return array(); + } + + /** + * Parses the arguments for the element. + * + * @since 3.2.8 + * @param array $args The arguments for the element. + * @return array + */ + private function parse_args( array $args ) { + return wp_parse_args( $args, $this->defaults() ); + } +} diff --git a/src/HTML/CategorySelect.php b/src/HTML/CategorySelect.php new file mode 100644 index 00000000000..4fd607134d4 --- /dev/null +++ b/src/HTML/CategorySelect.php @@ -0,0 +1,118 @@ +args = $this->parse_args( $args ); + } + + /** + * Gets the HTML for the category select. + * + * @since 3.2.0 + * @return string + */ + public function get() { + if ( empty( $this->args['options'] ) ) { + $this->args['options'] = $this->get_categories(); + } + + $select = new Select( $this->args ); + + return $select->get(); + } + + /** + * Parses the arguments for the category select. + * + * @since 3.2.0 + * @param array $args Arguments for the category select. + * @return array + */ + private function parse_args( $args ) { + $category_labels = edd_get_taxonomy_labels( 'download_category' ); + + return wp_parse_args( + $args, + array( + 'name' => 'edd_categories', + 'selected' => '', + 'multiple' => false, + /* translators: %s: Download Category taxonomy name */ + 'show_option_all' => sprintf( _x( 'All %s', 'plural: Example: "All Categories"', 'easy-digital-downloads' ), $category_labels['name'] ), + 'show_option_none' => false, + 'data' => array( + /* translators: %s: Download Category taxonomy name */ + 'placeholder' => sprintf( _x( 'Search %s', 'plural: Example: "Search Download Categories"', 'easy-digital-downloads' ), $category_labels['name'] ), + 'search-type' => 'download_category', + /* translators: %s: Download Category taxonomy name */ + 'search-placeholder' => sprintf( _x( 'Search %s', 'plural: Example: "Search Download Categories"', 'easy-digital-downloads' ), $category_labels['name'] ), + ), + /* translators: %s: Download Category taxonomy name */ + 'placeholder' => sprintf( _x( 'Choose %s', 'plural: Example: "Choose one or more Download Categories"', 'easy-digital-downloads' ), $category_labels['name'] ), + ) + ); + } + + /** + * Gets the categories as options. + * + * @since 3.2.0 + * @return array + */ + private function get_categories() { + $categories = get_terms( $this->get_category_args() ); + $options = array(); + + foreach ( $categories as $category ) { + $options[ absint( $category->term_id ) ] = esc_html( $category->name ); + } + + return $options; + } + + /** + * Gets the arguments for the category query. + * + * @since 3.2.0 + * @return array + */ + private function get_category_args() { + $args = apply_filters( 'edd_category_dropdown', array() ); + $args['taxonomy'] = 'download_category'; + $args['hierarchical'] = false; + if ( ! empty( $this->args['number'] ) ) { + $args['number'] = $this->args['number']; + } + + return $args; + } +} diff --git a/src/HTML/Checkbox.php b/src/HTML/Checkbox.php new file mode 100644 index 00000000000..5f3e646f088 --- /dev/null +++ b/src/HTML/Checkbox.php @@ -0,0 +1,87 @@ + + args['options']['disabled'] ) ) { + ?> + disabled + args['options']['readonly'] ) ) { + ?> + readonly + args['value'] ) ) { + ?> + value="args['value'] ); ?>" + args['current'] ) ); + echo $this->get_data_elements(); + ?> + /> + args['label'] ) ) { + ?> + + null, + 'current' => null, + 'class' => 'edd-checkbox', + 'options' => array( + 'disabled' => false, + 'readonly' => false, + ), + 'label' => '', + 'value' => null, + ); + } +} diff --git a/src/HTML/CheckboxToggle.php b/src/HTML/CheckboxToggle.php new file mode 100644 index 00000000000..736962cd5a9 --- /dev/null +++ b/src/HTML/CheckboxToggle.php @@ -0,0 +1,119 @@ +args['id'] ) ? $this->args['id'] : $this->args['name']; + ob_start(); + ?> +
    + args['current'] ) ); + if ( ! empty( $this->args['options']['disabled'] ) ) { + ?> + disabled + args['options']['readonly'] ) ) { + ?> + readonly + get_data_elements(); + ?> + /> + +
    + null, + 'current' => null, + 'class' => '', + 'options' => array( + 'disabled' => false, + 'readonly' => false, + 'inverse' => false, + ), + 'label' => '', + 'value' => 1, + 'tooltip' => false, + ); + } + + /** + * Gets the base CSS classes for the select element. + * + * @since 3.2.8 + * @return array + */ + protected function get_base_classes(): array { + $classes = array( + 'edd-toggle', + ); + + if ( ! empty( $this->args['options']['inverse'] ) ) { + $classes[] = 'inverse'; + } + + return $classes; + } + + /** + * Renders the tooltip if one is set. + * + * @since 3.3.6 + * @return void + */ + private function maybe_do_tooltip() { + if ( empty( $this->args['tooltip'] ) ) { + return; + } + $tooltip = new Tooltip( $this->args['tooltip'] ); + $tooltip->output(); + } +} diff --git a/src/HTML/Elements.php b/src/HTML/Elements.php new file mode 100644 index 00000000000..e3b089a616c --- /dev/null +++ b/src/HTML/Elements.php @@ -0,0 +1,658 @@ +get(); + } + + /** + * 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' ); + } + + // If bundles are not allowed, get a few more products to account for the ones that will be removed. + if ( ! $args['bundles'] && 30 === $args['number'] ) { + $product_args['posts_per_page'] = 40; + } + + $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 Arguments for the dropdown. + * + * @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 a selected customer has been specified, we need to ensure it's in the initial list of customers displayed. + if ( ! empty( $args['selected'] ) && ! array_key_exists( $args['selected'], $options ) ) { + $customer = edd_get_customer( $args['selected'] ); + + if ( $customer ) { + $options[ absint( $args['selected'] ) ] = esc_html( $customer->name . ' (' . $customer->email . ')' ); + } + } + + return $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'], + ) + ); + } + + /** + * Renders an HTML Dropdown of all the Users + * + * @since 2.6.9 + * + * @param array $args Arguments for the dropdown. + * + * @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 ); + } + } + } + } + + return $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'], + ) + ); + } + + /** + * 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; + } + + $discount_args['status'] = ! empty( $status ) ? $status : array( 'active', 'expired', 'inactive', 'archived' ); + + $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' ); + } + + return $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'], + ) + ); + } + + /** + * 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' ); + + return $this->select( + array( + 'name' => $name, + 'selected' => $selected, + 'options' => $options, + 'show_option_all' => sprintf( + /* translators: %s: Download Category taxonomy name */ + _x( 'All %s', 'plural: Example: "All Categories"', 'easy-digital-downloads' ), + $category_labels['name'] + ), + 'show_option_none' => false, + ) + ); + } + + /** + * 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; + } + + return $this->select( + array( + 'name' => $name, + 'id' => $id . '_' . $name, + 'selected' => $selected, + 'options' => $options, + 'show_option_all' => false, + 'show_option_none' => false, + ) + ); + } + + /** + * 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; + } + + return $this->select( + array( + 'name' => $name, + 'id' => $id . '_' . $name, + 'selected' => $selected, + 'options' => $options, + 'show_option_all' => false, + 'show_option_none' => false, + ) + ); + } + + /** + * 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' => false, + 'show_option_none' => false, + 'show_option_empty' => __( '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(); + } + $options = edd_get_shop_states( $country ); + if ( 'GB' === $country && ! empty( $region ) && ! array_key_exists( $region, $options ) ) { + $legacy_states = include EDD_PLUGIN_DIR . 'i18n/states-gb-legacy.php'; + if ( array_key_exists( $region, $legacy_states ) ) { + $options[ $region ] = $legacy_states[ $region ]; + + // Sort the states alphabetically. + asort( $options ); + } + } + $args = wp_parse_args( + $args, + array( + 'name' => 'edd_regions', + 'class' => 'edd_regions_filter', + 'options' => $options, + 'chosen' => true, + 'selected' => $region, + 'show_option_none' => false, + 'placeholder' => __( 'Choose a Region', 'easy-digital-downloads' ), + 'show_option_empty' => __( 'All Regions', 'easy-digital-downloads' ), + 'show_option_all' => false, + '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 + * @since 3.2.8 Updated to use the Select class. + * @param array $args The arguments for the dropdown. + * @return string + */ + public function select( $args = array() ) { + $select = new Select( $args ); + + return $select->get(); + } + + /** + * Renders an HTML Checkbox + * + * @since 1.9 + * @since 3.0 Added `label` argument. + * @since 3.2.8 Updated to use the Checkbox class. + * @param array $args Arguments for the checkbox. + * @return string Checkbox HTML code + */ + public function checkbox( $args = array() ) { + $checkbox = new Checkbox( $args ); + + return $checkbox->get(); + } + + /** + * Renders an HTML Text field + * + * @since 1.5.2 + * @since 3.2.8 Updated to use the Text class. + * @param array $args Arguments for the text field. + * @return string Text field + */ + public function text( $args = array() ) { + if ( func_num_args() > 1 ) { + $legacy_args = func_get_args(); + $args = array( + 'name' => $legacy_args[0], + 'value' => isset( $legacy_args[1] ) ? $legacy_args[1] : '', + 'label' => isset( $legacy_args[2] ) ? $legacy_args[2] : '', + 'desc' => isset( $legacy_args[3] ) ? $legacy_args[3] : '', + ); + } + + $text = new Text( $args ); + + return $text->get(); + } + + /** + * 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 + * @since 3.2.8 Updated to use the Textarea class. + * @param array $args Arguments for the textarea. + * @return string textarea + */ + public function textarea( $args = array() ) { + $textarea = new Textarea( $args ); + + return $textarea->get(); + } + + /** + * Renders an ajax user search field + * + * @since 2.0 + * + * @param array $args Arguments for the field. + * + * @return string text field with ajax search + */ + public function ajax_user_search( $args = array() ) { + + $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/src/HTML/Multicheck.php b/src/HTML/Multicheck.php new file mode 100644 index 00000000000..9916b8d04cc --- /dev/null +++ b/src/HTML/Multicheck.php @@ -0,0 +1,143 @@ + +
    + args['legend'] ) ) : ?> + args['legend'] ); ?> + + args['options'] as $key => $data ) { + $label = ! empty( $data['label'] ) ? $data['label'] : $data; + $checked = (int) (bool) ! empty( $data['checked'] ); + $classes = array( + 'edd-form-group__control--wrap', + ); + if ( ! empty( $data['classes'] ) ) { + $classes = array_merge( $classes, $data['classes'] ); + } + $inner_classes = array( + 'edd-form-group__control', + ); + if ( ! empty( $this->args['toggle'] ) ) { + $inner_classes[] = 'edd-toggle'; + } + ?> +
    +
    + do_icon( $data ); ?> + + + + > +
    + + do_tooltip( $data ); ?> +
    +
    + +

    + +
    + +
    + null, + 'class' => 'multicheck', + 'options' => array(), // key => array( label => label, disabled => bool, checked => bool ). + 'legend' => '', + 'toggle' => false, + ); + } + + /** + * Gets the base CSS classes for the select element. + * + * @since 3.3.5 + * @return array + */ + protected function get_base_classes(): array { + $classes = array( + 'edd-form-group', + 'edd-multicheck', + ); + + if ( ! empty( $this->args['toggle'] ) ) { + $classes[] = 'edd-form-group--has-toggle'; + } + + return $classes; + } + + /** + * Outputs the icon for the multicheck. + * + * @since 3.3.5 + * @param array $data The data for the icon. + */ + private function do_icon( $data ) { + if ( empty( $data['icon'] ) ) { + return; + } + echo $data['icon']; + } + + /** + * Outputs the tooltip for the multicheck. + * + * @since 3.3.5 + * @param array $data The data for the tooltip. + */ + private function do_tooltip( $data ) { + if ( empty( $data['tooltip'] ) ) { + return; + } + $tooltip = new Tooltip( $data['tooltip'] ); + $tooltip->output(); + } +} diff --git a/src/HTML/Number.php b/src/HTML/Number.php new file mode 100644 index 00000000000..018ba05eaf6 --- /dev/null +++ b/src/HTML/Number.php @@ -0,0 +1,134 @@ +args['label'] ) ) { + ?> + + + get_data_elements(); + if ( $this->args['disabled'] ) : + ?> + disabled + args['readonly'] ) : + ?> + readonly + args['required'] ) : + ?> + required + args['min'] ) : + ?> + min="args['min'] ); ?>" + args['max'] ) : + ?> + max="args['max'] ); ?>" + args['step'] ) ) : + ?> + step="args['step'] ); ?>" + args['datalist'] ) ) : + ?> + list="args['name'] ); ?>-datalist" + + /> + args['datalist'] ) ) { + ?> + + args['datalist'] as $option ) { + ?> + + + + args['desc'] ) ) { + ?> + args['desc'] ) ); ?> + '', + 'name' => 'number', + 'value' => '', + 'label' => '', + 'desc' => '', + 'placeholder' => '', + 'class' => '', + 'disabled' => false, + 'data' => false, + 'required' => false, + 'min' => '', + 'max' => '', + 'step' => '', + 'datalist' => array(), + 'readonly' => false, + ); + } +} diff --git a/src/HTML/ProductSelect.php b/src/HTML/ProductSelect.php new file mode 100644 index 00000000000..07ca400381f --- /dev/null +++ b/src/HTML/ProductSelect.php @@ -0,0 +1,301 @@ +get_options(); + + // Update options to remove any excluded products. + if ( ! empty( $this->args['excluded_products'] ) && is_array( $this->args['excluded_products'] ) ) { + $excluded_products = array(); + foreach ( $this->args['excluded_products'] as $exclusion ) { + if ( array_key_exists( $exclusion, $options ) ) { + unset( $options[ $exclusion ] ); + } + $excluded_products[] = absint( $exclusion ); + } + $this->args['data']['excluded-products'] = implode( ',', array_filter( $excluded_products ) ); + } + + $this->args['class'] = $this->get_classes(); + $this->args['options'] = $options; + $this->args['show_option_none'] = false; + + // The product select must always show an empty option. + if ( empty( $this->args['show_option_empty'] ) ) { + $this->args['show_option_empty'] = sprintf( + /* translators: %s: Download plural label */ + __( 'All %s', 'easy-digital-downloads' ), + edd_get_label_plural() + ); + } + + return parent::get(); + } + + /** + * Gets the default arguments for the element. + * + * @since 3.2.8 + * @return array + */ + protected function defaults() { + return array( + 'name' => 'products', + 'id' => 'products', + 'class' => array(), + 'multiple' => false, + 'selected' => 0, + 'chosen' => false, + 'number' => 30, + 'bundles' => true, + 'variations' => false, + 'show_variations_only' => false, + 'placeholder' => sprintf( + /* translators: %s: Download singular label */ + __( 'Choose a %s', 'easy-digital-downloads' ), + edd_get_label_singular() + ), + 'data' => array( + 'search-type' => 'download', + 'search-placeholder' => sprintf( + /* translators: %s: Download plural label */ + _x( 'Search %s', 'Noun: Download plural label for product select input', 'easy-digital-downloads' ), + edd_get_label_plural() + ), + ), + 'required' => false, + 'products' => array(), + 'show_option_empty' => sprintf( + /* translators: %s: Download plural label */ + _x( 'All %s', 'Noun: Download plural label for selecting All Downloads', 'easy-digital-downloads' ), + edd_get_label_plural() + ), + ); + } + + /** + * Gets the options for the select element. + * + * @since 3.2.8 + * @return array + */ + private function get_options() { + $products = $this->get_products(); + $options = array(); + if ( $products ) { + foreach ( $products as $product ) { + // If bundles are not allowed, skip any products that are bundles. + if ( ! $this->args['bundles'] && 'bundle' === edd_get_download_type( $product->ID ) ) { + continue; + } + + // If a product has no variations, just add it to the list and continue. + if ( ! edd_has_variable_prices( $product->ID ) ) { + $options[ absint( $product->ID ) ] = esc_html( $product->post_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 === $this->args['variations'] || ! $this->args['show_variations_only'] ) { + $options[ absint( $product->ID ) ] = $this->get_product_title( $product ); + } + + $variations = $this->get_variations( $product ); + if ( ! empty( $variations ) ) { + $options = $options + $variations; + } + } + } + + if ( empty( $this->args['selected'] ) ) { + return $options; + } + + // The selected item(s) always need to be in the list, so we make sure to add them if missing. + $selected = (array) $this->args['selected']; + foreach ( $selected as $item ) { + if ( array_key_exists( $item, $options ) ) { + continue; + } + + $missing_item = $this->get_missing_selected_product( $item ); + if ( ! empty( $missing_item ) ) { + $options = $options + $missing_item; + } + } + + return $options; + } + + /** + * Gets the products for the select element. + * + * @since 3.2.8 + * @return array + */ + private function get_products() { + $products = $this->args['products']; + if ( empty( $this->args['products'] ) ) { + $products = EDD()->html->get_products( $this->args ); + } + + if ( empty( $this->args['selected'] ) ) { + return $products; + } + + $selected_items = (array) $this->args['selected']; + $existing_ids = wp_list_pluck( $products, 'ID' ); + foreach ( $selected_items as $selected_item ) { + if ( 'download' !== get_post_type( $selected_item ) ) { + continue; + } + 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; + } + } + } + + return $products; + } + + /** + * Gets the classes for the select element. + * + * @since 3.2.8 + * @return array + */ + private function get_classes() { + $classes = $this->args['class']; + if ( ! is_array( $classes ) ) { + $classes = explode( ' ', $classes ); + } + if ( ! $this->args['bundles'] ) { + $classes[] = 'no-bundles'; + } + + if ( $this->args['variations'] ) { + $classes[] = 'variations'; + } + + if ( $this->args['show_variations_only'] ) { + $classes[] = 'variations-only'; + } + + if ( ! empty( $this->args['exclude_current'] ) ) { + $classes[] = 'exclude-current'; + } + + return $classes; + } + + /** + * Gets the missing selected product. + * + * @since 3.2.8 + * @param string $item Item to check. + * @return array + */ + private function get_missing_selected_product( $item ) { + $options = array(); + $parsed_item = edd_parse_product_dropdown_value( $item ); + $download_id = (int) $parsed_item['download_id']; + + if ( 'download' !== get_post_type( $download_id ) ) { + return $options; + } + + if ( ! is_null( $parsed_item['price_id'] ) ) { + $prices = edd_get_variable_prices( $download_id ); + foreach ( $prices as $key => $value ) { + $name = ! empty( $value['name'] ) ? $value['name'] : ''; + + if ( $name && (int) $parsed_item['price_id'] === (int) $key ) { + $option_key = absint( $download_id ) . '_' . $key; + $options[ $option_key ] = esc_html( get_the_title( $download_id ) . ': ' . $name ); + } + } + } else { + $options[ $download_id ] = get_the_title( $download_id ); + } + + return $options; + } + + /** + * Gets the product title for the select element. + * + * @since 3.2.8 + * @param \WP_Post $product Product post object. + * @return string + */ + private function get_product_title( $product ) { + $title = esc_html( $product->post_title ); + if ( ! $this->args['show_variations_only'] ) { + $title .= ' (' . __( 'All Price Options', 'easy-digital-downloads' ) . ')'; + } + + return $title; + } + + /** + * Gets the product variations for the select element. + * + * @since 3.2.8 + * @param \WP_Post $product Product post object. + * @return array + */ + private function get_variations( $product ) { + if ( empty( $this->args['variations'] ) ) { + return array(); + } + $prices = edd_get_variable_prices( $product->ID ); + if ( empty( $prices ) ) { + return array(); + } + + $options = array(); + foreach ( $prices as $key => $value ) { + if ( ! empty( $value['name'] ) ) { + $options[ absint( $product->ID ) . '_' . $key ] = esc_html( $product->post_title . ': ' . $value['name'] ); + } + } + + return $options; + } +} diff --git a/src/HTML/Select.php b/src/HTML/Select.php new file mode 100644 index 00000000000..3ca438dd7cd --- /dev/null +++ b/src/HTML/Select.php @@ -0,0 +1,178 @@ +maybe_update_selected(); + ob_start(); + ?> + + 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, + 'show_option_empty' => false, + ); + } + + /** + * Gets the base CSS classes for the select element. + * + * @since 3.2.8 + * @return array + */ + protected function get_base_classes(): array { + $base_classes = array( + 'edd-select', + ); + if ( $this->args['chosen'] ) { + $base_classes[] = 'edd-select-chosen'; + if ( is_rtl() ) { + $base_classes[] = ' chosen-rtl'; + } + } + + return $base_classes; + } + + /** + * Checks if the value is selected. + * + * @since 3.2.8 + * @param string|int $value The value to check. This could be a string or an integer. + * @return bool + */ + private function is_selected( $value ) { + if ( $this->args['multiple'] ) { + return selected( true, in_array( (string) $value, $this->args['selected'], true ), false ); + } + + if ( ( ! empty( $this->args['selected'] ) || is_numeric( $this->args['selected'] ) ) && ! is_array( $this->args['selected'] ) ) { + return selected( $this->args['selected'], $value, false ); + } + + return false; + } + + /** + * Updates the selected value. If the select is a multiple select, the selected value will be an array of strings. + * This is only for comparison purposes. + * + * @since 3.2.10 + * @return void + */ + private function maybe_update_selected() { + if ( $this->args['multiple'] || is_array( $this->args['selected'] ) ) { + $this->args['selected'] = array_map( 'strval', (array) $this->args['selected'] ); + } + } +} diff --git a/src/HTML/Text.php b/src/HTML/Text.php new file mode 100644 index 00000000000..5a311f81cc4 --- /dev/null +++ b/src/HTML/Text.php @@ -0,0 +1,102 @@ +args['include_span'] ) : + ?> + + args['label'] ) ) { + ?> + + args['desc'] ) ) { + ?> + args['desc'] ); ?> + + get_data_elements(); + if ( $this->args['disabled'] ) : + ?> + disabled + args['required'] ) : + ?> + required + + /> + args['include_span'] ) : ?> + + '', + 'name' => 'text', + 'value' => '', + 'label' => '', + 'desc' => '', + 'placeholder' => '', + 'class' => 'regular-text', + 'disabled' => false, + 'autocomplete' => '', + 'data' => false, + 'required' => false, + 'include_span' => true, + ); + } +} diff --git a/src/HTML/Textarea.php b/src/HTML/Textarea.php new file mode 100644 index 00000000000..fb340588eab --- /dev/null +++ b/src/HTML/Textarea.php @@ -0,0 +1,80 @@ + + + args['label'] ) ) { + ?> + + + + + args['desc'] ) ) { + ?> +

    args['desc'] ); ?>

    + +
    + 'textarea', + 'value' => '', + 'label' => '', + 'desc' => null, + 'class' => 'large-text', + 'disabled' => false, + ); + } +} diff --git a/src/HTML/TimelineTooltip.php b/src/HTML/TimelineTooltip.php new file mode 100644 index 00000000000..d29e76fc5b9 --- /dev/null +++ b/src/HTML/TimelineTooltip.php @@ -0,0 +1,150 @@ + '', + 'items' => array(), + 'max_items' => 5, + 'slice_from' => 'start', + 'more_position' => 'bottom', + 'dashicon' => 'dashicons-clock', + ); + } + + /** + * Gets the HTML for the tooltip. + * + * @since 3.2.7 + * @return string + */ + public function get() { + // If there are no items passed, return an empty string. + if ( empty( $this->args['items'] ) ) { + return ''; + } + + $title = $this->get_title_markup(); + $opening_ul = sprintf( + '
      ', // As this is content added to a title attribute, we have to use single quotes here. + $this->array_to_css_string( array( 'timeline' ) ) + ); + + $list_item_markup = ''; + + // Loop over the items, and append them to the content string. + foreach ( $this->parse_list_items() as $list_item ) { + $list_item_markup .= sprintf( + '
    • %s
    • ', // As this is content added to a title attribute, we have to use single quotes here. + $list_item + ); + } + + $closing_ul = '
    '; + + // Build the tooltip content string. + $tooltip_content = sprintf( + '%1$s%2$s%3$s%4$s', + $title, + $opening_ul, + $list_item_markup, + $closing_ul + ); + + // Return the icon for the tooltip, with the tooltip content added as the title attribute. + return sprintf( + '', + $this->get_css_class_string(), + $tooltip_content + ); + } + + /** + * Gets the list items for the tooltip. + * + * @since 3.2.7 + * + * @return array + */ + private function parse_list_items() { + $items = $this->args['items']; + $total_items = count( $items ); + $max_items = $this->args['max_items']; + $more_position = $this->args['more_position']; + + // Reduce the number of items in the array to the max number of items. + if ( false !== $max_items ) { + $items = 'start' === $this->args['slice_from'] ? + array_slice( $items, 0, $max_items ) : + array_slice( $items, -( $max_items ) ); + } + + // Initialize the list items array. + $list_items = array(); + + foreach ( $items as $item ) { + // If the item is numeric (a timestamp) convert it to a date string. + if ( is_numeric( $item ) ) { + $item = edd_date_i18n( $item, get_option( 'date_format' ) . ' H:i:s' ) . ' ' . edd_get_timezone_abbr(); + } + + $list_items[] = esc_html( $item ); + } + + if ( + false !== $max_items && // If there is a max number of items. + false !== $more_position && // .. and if the more position is not false. + $total_items > $max_items // ...and if the total number of items is greater than the max number of items. + ) { + $more_count = $total_items - $max_items; + + $more_items = sprintf( + /* translators: %s: number of additional items that are not being displayed. */ + __( '%s More', 'easy-digital-downloads' ), + $more_count > 10 ? + '10+' : + $more_count + ); + + // If at the top, use array_unshift, otherwise add to the array at the bottom. + if ( 'top' === $more_position ) { + array_unshift( $list_items, $more_items ); + } else { + $list_items[] = $more_items; + } + } + + return $list_items; + } +} diff --git a/src/HTML/Tooltip.php b/src/HTML/Tooltip.php new file mode 100644 index 00000000000..314c23cfab4 --- /dev/null +++ b/src/HTML/Tooltip.php @@ -0,0 +1,128 @@ +args['content'] ) ) { + return ''; + } + + $tooltip_content = $this->get_title_markup(); + $tooltip_content .= $this->separate_title_content( $tooltip_content ); + $tooltip_content .= $this->args['content']; + + // Return the icon for the tooltip, with the tooltip content added as the title attribute. + return sprintf( + '', + $this->get_css_class_string(), + $tooltip_content + ); + } + + /** + * Gets the default arguments for the element. + * + * @since 3.2.8 + * @return array + */ + protected function defaults() { + return array( + 'title' => '', + 'content' => '', + 'dashicon' => 'dashicons-editor-help', + 'line_break' => false, + ); + } + + /** + * Gets the base classes for an element which cannot be overwritten. + * + * @since 3.2.8 + * @return array + */ + protected function get_base_classes(): array { + return array( 'edd-help-tip', 'dashicons', $this->args['dashicon'] ); + } + + /** + * Gets the HTML for the title of the tooltip. + * + * @since 3.2.7 + * @return string + */ + protected function get_title_markup() { + // Ensure the title only contains allowed HTML tags and trim it up. + $this->args['title'] = trim( + wp_kses( + $this->args['title'], + array( + 'em' => array(), + 'strong' => array(), + ) + ) + ); + + // After sanitizing, if the title is empty, return an empty string. + if ( empty( $this->args['title'] ) ) { + return ''; + } + + return sprintf( + '%s', + $this->args['title'] + ); + } + + /** + * Adds padding to the content if the title is not empty. + * + * @since 3.2.8 + * @param string $content The content to add padding to. + * @return string + */ + private function separate_title_content( $content ) { + if ( empty( $content ) ) { + return ''; + } + + if ( ! empty( $this->args['line_break'] ) ) { + return '
    '; + } + + // Check if the title ends with a punctuation mark. If not, add a colon. + $last_character = substr( $this->args['title'], -1 ); + $separator = ' '; + if ( ! in_array( $last_character, $this->get_final_punctuation(), true ) ) { + $separator = ':' . $separator; + } + + return $separator; + } + + /** + * Gets the final punctuation marks. + * + * @since 3.2.8 + * @return array + */ + private function get_final_punctuation() { + return array( '.', '!', '?', ':' ); + } +} diff --git a/src/HTML/Upload.php b/src/HTML/Upload.php new file mode 100644 index 00000000000..29830e9efa9 --- /dev/null +++ b/src/HTML/Upload.php @@ -0,0 +1,100 @@ + +
    + get_data_elements(); ?> + > + +
    + args['desc'] ) ) { + ?> +

    args['desc'] ); ?>

    + '', + 'name' => 'upload', + 'value' => '', + 'label' => '', + 'desc' => '', + 'class' => 'upload', + ); + } + + /** + * Gets the ID for the upload field. + * + * @since 3.3.0 + * @return string + */ + private function get_id() { + return $this->args['id'] ?? $this->args['name']; + } + + /** + * Gets the data input attribute. + * + * @since 3.3.0 + * @return string + */ + private function get_data_input() { + $id = $this->get_id(); + $id = str_replace( '[', '\\[', $id ); + $id = str_replace( ']', '\\]', $id ); + + return '#' . $id; + } +} diff --git a/src/Integrations/Registry.php b/src/Integrations/Registry.php new file mode 100644 index 00000000000..26a9822e762 --- /dev/null +++ b/src/Integrations/Registry.php @@ -0,0 +1,52 @@ + 'register_admin_integrations', + ); + } + + /** + * Registers the integrations. + * + * @since 3.2.4 + * @return void + */ + public function register_admin_integrations() { + $integrations = array( + WPCode::class, + ); + + foreach ( $integrations as $integration ) { + if ( ! class_exists( $integration ) ) { + continue; + } + + try { + $integration = new $integration(); + $integration->subscribe(); + } catch ( \Exception $e ) { + // Do nothing. + } + } + } +} diff --git a/src/Integrations/WPCode.php b/src/Integrations/WPCode.php new file mode 100644 index 00000000000..e56768e6d01 --- /dev/null +++ b/src/Integrations/WPCode.php @@ -0,0 +1,38 @@ +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; + } + + // If a request has recently failed, don't try again. + if ( $this->request_recently_failed() ) { + return false; + } + + $request = new \EDD\Utils\RemoteRequest( + add_query_arg( $this->get_body( $api_params ), $this->api_url ), + array( + 'timeout' => 15, + 'sslverify' => true, + ) + ); + + // If there was an API error, return false. + if ( is_wp_error( $request->response ) || ( 200 !== $request->code ) ) { + $this->log_failed_request(); + + return false; + } + + return json_decode( $request->body ); + } + + /** + * 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' => urlencode( home_url() ), + ) + ); + } + + /** + * Determines if a request has recently failed. + * + * @since 1.9.1 + * + * @return bool + */ + private function request_recently_failed() { + if ( ! $this->should_check_failed_request ) { + return false; + } + + $failed_request_details = get_option( $this->get_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->get_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. + * + * @since 3.3.0 + */ + private function log_failed_request() { + update_option( $this->get_failed_request_cache_key(), strtotime( '+1 hour' ) ); + } + + /** + * Retrieves the cache key for the failed requests option. + * + * @since 3.3.0 + * @return string The cache key for failed requests. + */ + private function get_failed_request_cache_key() { + return 'edd_failed_request_' . md5( $this->api_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..0a727b451b5 --- /dev/null +++ b/src/Licensing/License.php @@ -0,0 +1,330 @@ +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; + } + + /** + * Whether the license is expired. + * + * @since 3.2.0 + * @return bool + */ + public function is_expired() { + if ( ! empty( $this->license ) && 'expired' === $this->license ) { + return true; + } + + return ( ! empty( $this->error ) && 'expired' === $this->error ); + } + + /** + * Only allow certain keys to be modified. + * + * @since 3.1.1 + * @return array + */ + private function get_editable_keys() { + return array( 'license', 'error', 'success', 'pass_id', 'subscription', 'subscription_id', 'expires' ); + } +} diff --git a/src/Licensing/Messages.php b/src/Licensing/Messages.php new file mode 100644 index 00000000000..bf64ca1f5d3 --- /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..8c7699f5617 --- /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..432836ab289 --- /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/Logging.php b/src/Logging.php new file mode 100644 index 00000000000..baa3fdb9a31 --- /dev/null +++ b/src/Logging.php @@ -0,0 +1,770 @@ +log_types(), true ); + } + + /** + * 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 ) && '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}"; + + return is_callable( $func ) + ? call_user_func( $func, $r ) + : false; + } + + /** + * 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}"; + + return is_callable( $func ) + ? call_user_func( $func, $r ) + : 0; + } + + /** + * 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 The old log 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 Arguments to parse. + * @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() { + if ( ! empty( $this->file ) ) { + return; + } + + $upload_dir = edd_get_upload_dir(); + $this->filename = wp_hash( home_url( '/' ) ) . '-edd-debug.log'; + $this->file = trailingslashit( $upload_dir ) . $this->filename; + if ( ! FileSystem::get_fs()->exists( $this->file ) ) { + FileSystem::maybe_move_file( $this->filename, $this->file ); + } + + if ( ! FileSystem::get_fs()->is_writable( $upload_dir ) ) { + $this->is_writable = 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() { + $this->setup_log_file(); + + 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 = ''; + $this->setup_log_file(); + + if ( FileSystem::get_fs()->exists( $this->file ) ) { + if ( ! FileSystem::get_fs()->is_writable( $this->file ) ) { + $this->is_writable = false; + } + + $file = FileSystem::get_fs()->get_contents( $this->file ); + } else { + FileSystem::get_fs()->put_contents( $this->file, '' ); + FileSystem::get_fs()->chmod( $this->file, 0664 ); + } + + return $file; + } + + /** + * Write the log message. + * + * @access protected + * @since 2.8.7 + */ + protected function write_to_log( $message = '' ) { + $this->setup_log_file(); + + 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->setup_log_file(); + FileSystem::get_fs()->delete( $this->file ); + + if ( FileSystem::get_fs()->exists( $this->file ) ) { + + // It's still there, so maybe server doesn't have delete rights. + FileSystem::get_fs()->chmod( $this->file, 0664 ); + FileSystem::get_fs()->delete( $this->file ); + + // See if it's still there... + if ( FileSystem::get_fs()->exists( $this->file ) ) { + FileSystem::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' ); + } +} diff --git a/src/Models/Notification.php b/src/Models/Notification.php new file mode 100644 index 00000000000..e2e311c5cae --- /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_updated ) ) ); + + return $data; + } + +} diff --git a/src/Orders/DeferredActions.php b/src/Orders/DeferredActions.php new file mode 100644 index 00000000000..158894476af --- /dev/null +++ b/src/Orders/DeferredActions.php @@ -0,0 +1,193 @@ + array( 'schedule_deferred_actions', 10, 1 ), + 'edd_after_payment_scheduled_actions' => array( 'run_deferred_actions', 10, 1 ), + 'edd_order_destroyed' => array( 'unschedule_deferred_actions', 10, 2 ), + ); + } + + /** + * Schedules the one time event via WP_Cron to fire after purchase actions. + * + * Is run on the edd_complete_purchase action. + * + * @since 3.2.0 + * @param int $payment_id The payment ID being processed. + */ + public static function schedule_deferred_actions( $payment_id ) { + edd_debug_log( 'Scheduling after order actions for order ID ' . $payment_id ); + + $use_cron = apply_filters( 'edd_use_after_payment_actions', true, $payment_id ); + if ( $use_cron ) { + $after_payment_delay = apply_filters( 'edd_after_payment_actions_delay', 30, $payment_id ); + + // Use time() instead of current_time( 'timestamp' ) to avoid scheduling the event in the past when server time + // and WordPress timezone are different. + \EDD\Cron\Events\SingleEvent::add( + time() + $after_payment_delay, + 'edd_after_payment_scheduled_actions', + array( $payment_id, false ) + ); + } + } + + /** + * Unschedule the deferred actions. + * + * @since 3.3.7 + * + * @param int $order_id The order ID. + * @param bool $destroyed If the order was destroyed. + */ + public static function unschedule_deferred_actions( $order_id, $destroyed ) { + if ( ! $destroyed ) { + return; + } + + \EDD\Cron\Events\SingleEvent::remove( + 'edd_after_payment_scheduled_actions', + array( $order_id, false ) + ); + } + + /** + * Runs the deferred actions. + * + * Is run on the edd_after_payment_scheduled_actions action. + * + * @since 3.2.0 + * @param int $payment_id The payment ID being processed. + * @param bool $force If we should run these actions, even if they've been run before. + * + * @return void + */ + public function run_deferred_actions( $payment_id = 0, $force = false ) { + if ( empty( $payment_id ) ) { + return; + } + + $order = edd_get_order( $payment_id ); + + // If the order is not found, return. + if ( empty( $order ) ) { + return; + } + + // If the order has already run the actions, return. + if ( ! empty( $order->date_actions_run ) && false === $force ) { + return; + } + + // If the order is not in a completed status, return. + if ( ! in_array( $order->status, edd_get_complete_order_statuses(), true ) ) { + return; + } + + /** + * In the event that during the order completion process, a timeout happens, + * ensure that all the order items have the correct status, to match the order itself. + * + * @see https://github.com/awesomemotive/easy-digital-downloads-pro/issues/77 + */ + $order_items = edd_get_order_items( + array( + 'order_id' => $payment_id, + 'status__not_in' => edd_get_deliverable_order_item_statuses(), + 'number' => 200, + ) + ); + + if ( ! empty( $order_items ) ) { + foreach ( $order_items as $order_item ) { + edd_update_order_item( + $order_item->id, + array( + 'status' => $order->status, + ) + ); + } + } + + $customer = edd_get_customer( $order->customer_id ); + + // If someone has hooked into the old action, we need to run it. + $this->maybe_trigger_legacy_action( $payment_id, $customer ); + + edd_debug_log( 'Running after order actions for order ID ' . $payment_id ); + + /** + * Runs **after** a purchase is marked as "complete". + * + * @since 3.2.0 + * + * @param int $order->id The order ID. + * @param EDD_Order $order The EDD_Order object containing all order data. + * @param EDD_Customer $customer The EDD_Customer object containing all customer data. + */ + do_action( 'edd_after_order_actions', $order->id, $order, $customer ); + + // Update the order with the date the actions were run in UTC. + $date = new Date( 'now', 'GMT' ); + edd_update_order( $order->id, array( 'date_actions_run' => $date->format( 'mysql' ) ) ); + + edd_debug_log( 'After order actions completed for order ID ' . $payment_id ); + } + + /** + * Runs the legacy action, if it's hooked. + * + * If you are calling this hook, you should move to edd_after_order_actions, which passes in an order object instead. + * + * @since 3.2.0 + * + * @param int $payment_id The payment ID being processed. + * @param EDD_Customer $customer The EDD_Customer object containing all customer data. + */ + private function maybe_trigger_legacy_action( $payment_id, $customer ) { + if ( has_action( 'edd_after_payment_actions' ) ) { + $payment = edd_get_payment( $payment_id ); + /** + * Runs **after** a purchase is marked as "complete". + * This is only run if something is hooked on it. + * + * @since 2.8 - Added EDD_Payment and EDD_Customer object to action. + * + * @param int $payment_id Payment ID. + * @param EDD_Payment $payment EDD_Payment object containing all payment data. + * @param EDD_Customer $customer EDD_Customer object containing all customer data. + */ + do_action( 'edd_after_payment_actions', $payment_id, $payment, $customer ); + } + } +} 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/Orders/Refunds/Number.php b/src/Orders/Refunds/Number.php new file mode 100644 index 00000000000..800548e8163 --- /dev/null +++ b/src/Orders/Refunds/Number.php @@ -0,0 +1,68 @@ +get_var( + $wpdb->prepare( + "SELECT COUNT(id) FROM {$wpdb->edd_orders} WHERE parent = %d AND type = 'refund'", + $order_id + ) + ); + + return intval( $existing_refunds_count ) + 1; + } +} diff --git a/src/Orders/Refunds/Validator.php b/src/Orders/Refunds/Validator.php new file mode 100644 index 00000000000..b906522e63f --- /dev/null +++ b/src/Orders/Refunds/Validator.php @@ -0,0 +1,531 @@ +order = $order; + $this->order_adjustments = $this->get_order_adjustments(); + $this->order_items_to_refund = $this->validate_and_format_order_items( $order_items ); + $this->adjustments_to_refund = $this->validate_and_format_adjustments( $adjustments ); + } + + /** + * Returns all refund-eligible adjustments associated with the order. + * Note that this doesn't exclude items that have already reached their refund max; it just + * returns all objects that could possibly be refunded. (Essentially `discount` adjustments + * are excluded.) + * + * @since 3.0 + * @return Order_Adjustment[] + */ + private function get_order_adjustments() { + $fees = $this->order->get_fees(); + $credits = edd_get_order_adjustments( + array( + 'object_id' => $this->order->id, + 'object_type' => 'order', + 'type' => 'credit', + ) + ); + + return array_merge( $fees, $credits ); + } + + /** + * Validates the supplied order items and does a little formatting. + * If `all` is supplied, then all items eligible for refund are included. + * + * @param array|string $order_items If `all` is supplied, then all items eligible for refund are included. + * + * @return array + * @throws Invalid_Argument If the order item ID is missing or not in the original order. + */ + private function validate_and_format_order_items( $order_items ) { + $keyed_order_items = array(); + + if ( 'all' === $order_items ) { + $order_items = $this->get_all_refundable_order_items(); + } + + if ( ! empty( $order_items ) && is_array( $order_items ) ) { + $order_item_ids = wp_list_pluck( $this->order->items, 'id' ); + + foreach ( $order_items as $order_item_data ) { + // order_item_id must be supplied and in the list attached to the original order. + if ( empty( $order_item_data['order_item_id'] ) || ! in_array( $order_item_data['order_item_id'], $order_item_ids ) ) { + throw Invalid_Argument::from( 'order_item_id', __METHOD__ ); + } + + $this->validate_required_fields( $order_item_data, __METHOD__ ); + + if ( ! isset( $order_item_data['total'] ) ) { + $order_item_data['total'] = $order_item_data['subtotal'] + $order_item_data['tax']; + } + + // Set the array key to be the order item ID for easier lookups as we go. + $keyed_order_items[ intval( $order_item_data['order_item_id'] ) ] = $order_item_data; + } + } + + return $keyed_order_items; + } + + /** + * Returns an array of all order items that can be refunded. + * This is used if `all` is supplied for order items. + * + * @since 3.0 + * @return array + */ + private function get_all_refundable_order_items() { + $order_items_to_refund = array(); + + foreach ( $this->order->items as $item ) { + if ( 'refunded' !== $item->status ) { + $order_items_to_refund[] = array_merge( + array( + 'order_item_id' => $item->id, + 'quantity' => $item->quantity, + ), + $item->get_refundable_amounts() + ); + } + } + + return $order_items_to_refund; + } + + /** + * Validates the supplied adjustments and does a little formatting. + * If `all` is supplied, then all adjustments eligible for refund are included. + * + * @param array|string $adjustments If `all` is supplied, then all adjustments eligible for refund are included. + * + * @return array + * @throws Invalid_Argument If the adjustment ID is missing or not in the original order. + */ + private function validate_and_format_adjustments( $adjustments ) { + $keyed_adjustments = array(); + + if ( 'all' === $adjustments ) { + $adjustments = $this->get_all_refundable_adjustments(); + } + + if ( ! empty( $adjustments ) && is_array( $adjustments ) ) { + $adjustment_ids = wp_list_pluck( $this->order_adjustments, 'id' ); + + foreach ( $adjustments as $adjustment_data ) { + // adjustment_id must be supplied and in the list attached to the original order/items. + if ( empty( $adjustment_data['adjustment_id'] ) || ! in_array( $adjustment_data['adjustment_id'], $adjustment_ids ) ) { + throw Invalid_Argument::from( 'adjustment_id', __METHOD__ ); + } + + $this->validate_required_fields( $adjustment_data, __METHOD__ ); + + if ( ! isset( $adjustment_data['total'] ) ) { + $adjustment_data['total'] = $adjustment_data['subtotal'] + $adjustment_data['tax']; + } + + // Set the array key to be the adjustment ID for easier lookups as we go. + $keyed_adjustments[ intval( $adjustment_data['adjustment_id'] ) ] = $adjustment_data; + } + } + + return $keyed_adjustments; + } + + /** + * Returns an array of all adjustments that can be refunded. + * This is used if `all` is supplied for adjustments. + * + * @since 3.0 + * @return array + */ + private function get_all_refundable_adjustments() { + $adjustments_to_refund = array(); + + foreach ( $this->order_adjustments as $adjustment ) { + if ( 'refunded' !== $adjustment->status ) { + $adjustments_to_refund[] = array_merge( + array( + 'adjustment_id' => $adjustment->id, + ), + $adjustment->get_refundable_amounts() + ); + } + } + + return $adjustments_to_refund; + } + + /** + * Validates required fields for both order items and taxes. + * + * @param array $input Input to be validated. + * @param string $context Context, for error message. + * + * @since 3.0 + * @throws Invalid_Argument If a required field is missing. + */ + private function validate_required_fields( $input, $context ) { + // subtotal and total are both required. + $required_fields = array( 'subtotal' ); + if ( edd_use_taxes() ) { + $required_fields[] = 'tax'; + } + + foreach ( $required_fields as $required_field ) { + if ( ! isset( $input[ $required_field ] ) ) { + throw Invalid_Argument::from( $required_field, $context ); + } + } + } + + /** + * Validates final amounts and calculates refund total. + * + * @throws \Exception If the refund amount is 0 or over the maximum allowed. + */ + public function validate_and_calculate_totals() { + $this->validate_order_item_amounts(); + $this->validate_adjustment_amounts(); + + // Some items or adjustments have to be selected to refund. + if ( empty( $this->order_items_to_refund ) && empty( $this->adjustments_to_refund ) ) { + throw new Exception( + __( 'No items have been selected to refund.', 'easy-digital-downloads' ) + ); + } + + // Refund amount cannot be 0. + if ( $this->total <= 0 ) { + throw new Exception( + sprintf( + /* translators: %s: 0.00 formatted in store currency */ + __( 'The refund amount must be greater than %s.', 'easy-digital-downloads' ), + edd_currency_filter( edd_format_amount( 0.00 ) ) + ) + ); + } + + // Overall refund total cannot be over total refundable amount. + $order_total = edd_get_order_total( $this->order->id ); + if ( $this->is_over_refund_amount( $this->total, $order_total ) ) { + throw new Exception( + sprintf( + /* translators: %s: maximum refund amount as formatted currency */ + __( 'The maximum refund amount is %s.', 'easy-digital-downloads' ), + edd_currency_filter( edd_format_amount( $order_total ) ) + ) + ); + } + } + + /** + * Validates the order item amounts. + * + * @throws \Exception If the refund amount is over the maximum allowed. + */ + private function validate_order_item_amounts() { + foreach ( $this->order->items as $item ) { + if ( ! array_key_exists( $item->id, $this->order_items_to_refund ) ) { + continue; + } + + $amount_to_refund = wp_parse_args( + $this->order_items_to_refund[ $item->id ], + array( + 'subtotal' => $item->subtotal, + 'tax' => $item->tax, + 'total' => $item->total, + ) + ); + + $this->order_items_to_refund[ $item->id ]['original_item_status'] = $this->validate_item_and_add_totals( $item, $amount_to_refund ); + } + } + + /** + * Validates the adjustment amounts. + * + * @throws \Exception If the refund amount is over the maximum allowed. + */ + private function validate_adjustment_amounts() { + foreach ( $this->order_adjustments as $adjustment ) { + if ( ! array_key_exists( $adjustment->id, $this->adjustments_to_refund ) ) { + continue; + } + + $amount_to_refund = wp_parse_args( + $this->adjustments_to_refund[ $adjustment->id ], + array( + 'subtotal' => $adjustment->subtotal, + 'tax' => $adjustment->tax, + 'total' => $adjustment->total, + ) + ); + + $this->adjustments_to_refund[ $adjustment->id ]['original_item_status'] = $this->validate_item_and_add_totals( $adjustment, $amount_to_refund ); + } + } + + /** + * Validates the amount attempting to be refunded against the total that can be refunded. + * + * The refund amount for each item cannot exceed the original amount minus what's already been refunded. + * Note: quantity is not checked because you might process multiple partial refunds for the same order item. + * + * @param Order_Item|Order_Adjustment $original_item Original item being refunded. + * @param array $amounts_to_refund Amounts *attempting* to be refunded. These will be + * matched against the maximums. + * + * @return string Either `refunded` if this is a complete refund, or `partially_refunded` if it's a partial. + * This should be the new status for the original item. + * @throws Exception If the refund amount is over the maximum allowed. + */ + private function validate_item_and_add_totals( $original_item, $amounts_to_refund ) { + $item_status = 'refunded'; + + $maximum_refundable_amounts = $original_item->get_refundable_amounts(); + + foreach ( array( 'subtotal', 'tax', 'total' ) as $column_name ) { + // Hopefully this should never happen, but just in case! + if ( ! array_key_exists( $column_name, $maximum_refundable_amounts ) ) { + throw new Exception( + sprintf( + /* translators: %s: type of amount being refunded (e.g. "subtotal" or "tax"). Not translatable at this time. */ + __( 'An unexpected error occurred while validating the maximum %s amount.', 'easy-digital-downloads' ), + $column_name + ) + ); + } + + // This is our fallback. + $attempted_amount = isset( $original_item->{$column_name} ) ? $original_item->{$column_name} : 0.00; + $maximum_amount = $maximum_refundable_amounts[ $column_name ]; + + // Only order items are included in the subtotal. + if ( ! $original_item instanceof Order_Item && 'subtotal' === $column_name ) { + continue; + } + + // But grab from specified amounts if available. It should always be available. + if ( isset( $amounts_to_refund[ $column_name ] ) ) { + $attempted_amount = $amounts_to_refund[ $column_name ]; + } + + if ( $this->is_over_refund_amount( $attempted_amount, $maximum_amount ) ) { + if ( $original_item instanceof Order_Item ) { + $error_message = sprintf( + /* + * Translators: + * %1$s - type of amount being refunded (subtotal, tax, or total); + * %1$s - product name; + * %3$s - maximum amount allowed for refund + */ + __( 'The maximum refund %1$s for the product "%2$s" is %3$s.', 'easy-digital-downloads' ), + $column_name, + $original_item->product_name, + edd_currency_filter( $maximum_refundable_amounts[ $column_name ] ) + ); + } else { + $error_message = sprintf( + /* + * Translators: + * %1$s - type of amount being refunded (subtotal, tax, or total); + * %1$s - adjustment description; + * %3$s - maximum amount allowed for refund + */ + __( 'The maximum refund %1$s for the adjustment "%2$s" is %3$s.', 'easy-digital-downloads' ), + $column_name, + $original_item->description, + edd_currency_filter( $maximum_refundable_amounts[ $column_name ] ) + ); + } + + throw new Exception( $error_message ); + } + + if ( 'total' === $column_name && $attempted_amount < $maximum_refundable_amounts['total'] ) { + $item_status = 'partially_refunded'; + } + + // If this is an adjustment, and it's _credit_, negate the amount because credit _reduces_ the total. + if ( $original_item instanceof Order_Adjustment && 'credit' === $original_item->type ) { + $attempted_amount = edd_negate_amount( $attempted_amount ); + } + + $this->{$column_name} += $attempted_amount; + } + + return $item_status; + } + + /** + * Returns an array of order items to refund. + * + * @since 3.0 + * @return array + */ + public function get_refunded_order_items() { + $order_items = array(); + + foreach ( $this->order->items as $item ) { + if ( array_key_exists( $item->id, $this->order_items_to_refund ) ) { + $defaults = $allowed_keys = $item->to_array(); + + if ( array_key_exists( 'original_item_status', $this->order_items_to_refund[ $item->id ] ) ) { + $allowed_keys['original_item_status'] = $this->order_items_to_refund[ $item->id ]['original_item_status']; + } + + $args = array_intersect_key( $this->order_items_to_refund[ $item->id ], $allowed_keys ); + $order_items[] = $this->set_common_item_args( wp_parse_args( $args, $defaults ) ); + } + } + + return $order_items; + } + + /** + * Returns an array of all adjustments to refund. + * + * @since 3.0 + * @return array + */ + public function get_refunded_adjustments() { + $order_item_adjustments = array(); + + foreach ( $this->order_adjustments as $adjustment ) { + if ( array_key_exists( $adjustment->id, $this->adjustments_to_refund ) ) { + $defaults = $allowed_keys = $adjustment->to_array(); + + if ( array_key_exists( 'original_item_status', $this->adjustments_to_refund[ $adjustment->id ] ) ) { + $allowed_keys['original_item_status'] = $this->adjustments_to_refund[ $adjustment->id ]['original_item_status']; + } + + $args = array_intersect_key( $this->adjustments_to_refund[ $adjustment->id ], $defaults ); + $order_item_adjustments[] = $this->set_common_item_args( wp_parse_args( $args, $defaults ) ); + } + } + + return $order_item_adjustments; + } + + /** + * Sets common arguments for refunded order items and adjustments. + * + * @param array $new_args + * + * @since 3.0 + * @return array + */ + private function set_common_item_args( $new_args ) { + // Set the `parent` to the original item ID. + if ( isset( $new_args['id'] ) ) { + $new_args['parent'] = $new_args['id']; + } + + // Negate amounts. + if ( array_key_exists( 'quantity', $new_args ) ) { + $new_args['quantity'] = edd_negate_int( $new_args['quantity'] ); + } + foreach ( array( 'subtotal', 'tax', 'total' ) as $field_to_negate ) { + if ( array_key_exists( $field_to_negate, $new_args ) ) { + $new_args[ $field_to_negate ] = edd_negate_amount( $new_args[ $field_to_negate ] ); + } + } + + // Strip out the keys we don't want. + $keys_to_remove = array( 'id', 'order_id', 'discount', 'date_created', 'date_modified', 'uuid' ); + $new_args = array_diff_key( $new_args, array_flip( $keys_to_remove ) ); + + // Status is always `complete`. + $new_args['status'] = 'complete'; + + return $new_args; + } + + /** + * Checks if the attempted refund amount is over the maximum allowed refund amount. + * + * @since 3.0 + * @param float $attempted_amount The amount to refund. + * @param float $maximum_amount The maximum amount which can be refunded. + * @return boolean + */ + private function is_over_refund_amount( $attempted_amount, $maximum_amount ) { + return edd_sanitize_amount( $attempted_amount ) > edd_sanitize_amount( $maximum_amount ); + } +} diff --git a/src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php b/src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php new file mode 100644 index 00000000000..95e5284c916 --- /dev/null +++ b/src/Reports/Data/Downloads/Earnings_By_Taxonomy_List_Table.php @@ -0,0 +1,386 @@ +show_warning() ) { + return array(); + } + global $wpdb; + + $dates = Reports\get_filter_value( 'dates' ); + $currency = Reports\get_filter_value( 'currencies' ); + + $taxonomies = edd_get_download_taxonomies(); + $taxonomies = array_map( 'sanitize_text_field', $taxonomies ); + + $placeholders = implode( ', ', array_fill( 0, count( $taxonomies ), '%s' ) ); + + $taxonomy__in = $wpdb->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 ) { + if ( isset( $taxonomies[ absint( $r->term_id ) ] ) ) { + $taxonomies[ absint( $r->term_id ) ]['object_ids'][] = absint( $r->object_id ); + continue; + } + $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() { + if ( $this->show_warning() ) { + ?> +

    + +

    +

    + + + +

    + show_warning ) ) { + return $this->show_warning; + } + + // If the user has already dismissed this warning, we don't need to show it again. + if ( get_transient( 'edd_earnings_by_taxonomy_show_report' ) ) { + $this->show_warning = false; + return $this->show_warning; + } + + // We only want to show this warning if there are more than 200 products or terms. + // This is, admittedly, an arbitrary number, but it's a good starting point. + $threshold = 200; + + if ( + wp_count_posts( 'download' )->publish > $threshold + || wp_count_terms( array( 'taxonomy' => edd_get_download_taxonomies() ) ) > $threshold + ) { + $this->show_warning = true; + } else { + $this->show_warning = false; + } + + return $this->show_warning; + } +} diff --git a/src/Reports/Data/Gateways/StripePaymentMethods.php b/src/Reports/Data/Gateways/StripePaymentMethods.php new file mode 100644 index 00000000000..26b2cdc318f --- /dev/null +++ b/src/Reports/Data/Gateways/StripePaymentMethods.php @@ -0,0 +1,229 @@ + 'stripe-payment-method', + 'plural' => 'stripe-payment-methods', + 'ajax' => false, + ) + ); + } + + /** + * Gets the name of the primary column. + * + * @since 3.3.5 + * @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 3.3.5 + * + * @param array $item Contains all the data of the payment method. + * @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 3.3.5 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'label' => __( 'Payment Method', 'easy-digital-downloads' ), + 'complete_sales' => __( 'Complete Sales', 'easy-digital-downloads' ), + 'pending_sales' => __( 'Pending / Failed Sales', 'easy-digital-downloads' ), + 'refunded_sales' => __( 'Refunded Sales', 'easy-digital-downloads' ), + 'total_sales' => __( 'Total Sales', 'easy-digital-downloads' ), + ); + } + + /** + * Outputs the reporting views. + * + * @since 3.3.5 + * @return void + */ + public function bulk_actions( $which = '' ) {} + + /** + * Retrieves all of the Stripe payment methods data. + * + * @since 3.3.5 + * @return array Payment gateways reports table data. + */ + public function get_data() { + + foreach ( \EDD\Gateways\Stripe\PaymentMethods::list() as $method => $label ) { + + $complete_count = $this->query( + $method, + array( + 'status' => edd_get_gross_order_statuses(), + ) + ); + if ( empty( $complete_count ) ) { + continue; + } + + $pending_count = $this->query( + $method, + array( + 'status' => edd_get_incomplete_order_statuses(), + ) + ); + + $refunded_count = $this->query( + $method, + array( + 'status' => array( 'complete' ), + 'type' => array( 'refund' ), + ) + ); + + $total_count = $this->query( + $method, + array() + ); + + $reports_data[] = array( + 'ID' => $method, + 'label' => $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 3.3.5 + * @uses StripePaymentMethods::get_columns() + * @uses StripePaymentMethods::get_sortable_columns() + * @uses StripePaymentMethods::reports_data() + * @return void + */ + 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(); + } + + /** + * Queries the orders table for the count of orders with a specific payment method. + * + * @since 3.3.5 + */ + private function query( $method, $args ) { + $filter = Reports\get_filter_value( 'dates' ); + $currency = Reports\get_filter_value( 'currencies' ); + + $args = wp_parse_args( + $args, + array( + 'gateway' => 'stripe', + 'type' => array( 'sale' ), + ) + ); + + $args['meta_query'] = $this->get_meta_query( $method ); + + if ( ! empty( $currency ) && 'convert' !== $currency ) { + $args['currency'] = $currency; + } + + if ( ! empty( $filter['range']['start'] ) ) { + $args['start'] = $filter['range']['start']->format( 'mysql' ); + } + + if ( ! empty( $filter['range']['end'] ) ) { + $args['end'] = $filter['range']['end']->format( 'mysql' ); + } + + return edd_count_orders( $args ); + } + + /** + * Retrieves the meta query for a specific payment method. + * Card payments are grouped with no payment method as the default. + * + * @since 3.3.5 + * @param string $method The payment method. + * @return array The meta query for the specified payment method. + */ + private function get_meta_query( $method ) { + if ( empty( $method ) ) { + return array( + 'relation' => 'OR', + array( + 'key' => 'stripe_payment_method_type', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'stripe_payment_method_type', + 'value' => 'card', + 'compare' => '=', + ), + ); + } + + return array( + array( + 'key' => 'stripe_payment_method_type', + 'value' => $method, + 'compare' => '=', + ), + ); + } +} diff --git a/src/RequirementsCheck.php b/src/RequirementsCheck.php new file mode 100644 index 00000000000..e872e0e2163 --- /dev/null +++ b/src/RequirementsCheck.php @@ -0,0 +1,519 @@ + array( + 'minimum' => '7.4', + 'name' => 'PHP', + 'exists' => true, + 'current' => false, + 'checked' => false, + 'met' => false, + ), + + // WordPress. + 'wp' => array( + 'minimum' => '6.2', + '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() { + + // 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 void + */ + 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() { + /* translators: 1: Requirement name, 2: Minimum version, 3: Current version */ + 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() { + /* translators: 1: Requirement name, 2: Minimum version */ + 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 The requirement parameters. + */ + 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 ( FileSystem::file_exists( $mofile_global1 ) ) { + load_textdomain( 'easy-digital-downloads', $mofile_global1 ); + + // Try to load from next global location. + } elseif ( FileSystem::file_exists( $mofile_global2 ) ) { + load_textdomain( 'easy-digital-downloads', $mofile_global2 ); + + // Try to load from next global location. + } elseif ( FileSystem::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/src/Sessions/Cart.php b/src/Sessions/Cart.php new file mode 100644 index 00000000000..ea1753a5315 --- /dev/null +++ b/src/Sessions/Cart.php @@ -0,0 +1,112 @@ +cart = $cart; + add_action( 'edd_post_remove_from_cart', array( $this, 'maybe_clear_session' ) ); + } + + /** + * Gets the cart contents. + * + * @since 3.3.0 + */ + public function get_contents() { + $this->cart->contents = EDD()->session->get( 'edd_cart' ); + + do_action( 'edd_cart_contents_loaded_from_session', $this->cart ); + } + + /** + * Gets the cart discounts. + * + * @since 3.3.0 + */ + public function get_discounts() { + $this->cart->discounts = EDD()->session->get( 'cart_discounts' ); + + do_action( 'edd_cart_discounts_loaded_from_session', $this->cart ); + } + + /** + * Empties the cart from the session. + * + * @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 cart cookies. + EDD()->session->set_cart_cookie( false ); + + // Remove any cart discounts. + $this->remove_all_discounts(); + $this->cart->contents = array(); + } + + /** + * Removes all discounts from the cart. + * + * @return void + */ + public function remove_all_discounts() { + EDD()->session->set( 'cart_discounts', null ); + $this->cart->discounts = array(); + + do_action( 'edd_cart_discounts_removed' ); + } + + /** + * Maybe clear the session. + * + * @since 3.3.0 + * @return void + */ + public function maybe_clear_session() { + if ( ! $this->cart->is_empty() ) { + return; + } + if ( $this->cart->has_discounts() ) { + return; + } + + EDD()->session->set_cart_cookie( false ); + } +} diff --git a/src/Sessions/Handler.php b/src/Sessions/Handler.php new file mode 100644 index 00000000000..6fddd8aeea3 --- /dev/null +++ b/src/Sessions/Handler.php @@ -0,0 +1,416 @@ +use_php_sessions() ) { + $this->manager = new Managers\PHP(); + } else { + $this->manager = new Managers\Database(); + } + add_action( 'shutdown', array( $this, 'save' ), 20 ); + add_action( 'wp_logout', array( $this, 'destroy' ) ); + add_action( 'edd_pre_add_to_cart', array( $this, 'reset' ) ); + add_action( 'edd_post_add_to_cart', array( $this, 'set_cart_cookie' ) ); + } + + /** + * Gets the session data for a specific key. + * Legacy method. + * + * @since 3.3.0 + * @param string $key The key to get. + * @param mixed $default_value The default value to return if the key is not set. + * @return mixed + */ + public function get( $key, $default_value = null ) { + if ( ! $this->is_active ) { + $this->maybe_start_session(); + } + $key = sanitize_key( $key ); + + if ( ! isset( $this->data[ $key ] ) ) { + return $default_value; + } + + return $this->data[ $key ]; + } + + /** + * Sets the session data for a specific key. + * Legacy method. + * + * @since 3.3.0 + * @param string $key The key to set. + * @param mixed $value The value to set. + * @return mixed + */ + public function set( $key, $value ) { + if ( ! $this->is_active ) { + $this->maybe_start_session( true ); + } + if ( ! is_array( $this->data ) ) { + $this->data = array(); + } + $key = sanitize_key( $key ); + if ( ! array_key_exists( $key, $this->data ) || $value !== $this->get( $key ) ) { + if ( ! empty( $value ) ) { + $this->data[ $key ] = $this->sanitize( $value ); + } elseif ( isset( $this->data[ $key ] ) ) { + unset( $this->data[ $key ] ); + } + $this->dirty = true; + if ( \EDD\Utils\Request::is_request( 'ajax' ) ) { + $this->save(); + } + } + + return isset( $this->data[ $key ] ) ? $this->data[ $key ] : null; + } + + /** + * Setup cookie and customer ID. + * Legacy method. + * + * @since 3.3.0 + * @param bool $needs_cookie Whether the session needs a cookie. + */ + public function maybe_start_session( $needs_cookie = false ) { + if ( $this->is_active ) { + return; + } + if ( ! $this->is_request_valid_for_session() ) { + return; + } + + /** + * Allow developers to disable session creation (legacy filter). + * + * @param bool $start_session Whether to start the session. + */ + if ( ! apply_filters( 'edd_start_session', true ) ) { + return; + } + + $cookie = $this->get_session_cookie(); + if ( ! $cookie && ! $needs_cookie ) { + return; + } + $this->manager->start(); + if ( ! $cookie ) { + $this->set_session_cookie(); + $this->data = $this->get_session_data(); + $this->is_active = true; + return; + } + + $this->session_key = $cookie['session_key']; + $this->data = $this->get_session_data(); + $this->session_expiry = $cookie['session_expiration']; + $this->session_expiring = $cookie['session_expiring']; + $this->has_cookie = true; + $this->is_active = true; + + if ( ! $this->is_session_cookie_valid() ) { + edd_debug_log( 'Session cookie invalid for: ' . $this->session_key ); + $this->destroy(); + $this->is_active = false; + } + + // Update session if it's close to expiring. + if ( time() > $this->session_expiring ) { + $this->session_expiry = $this->get_session_expiry( true ); + $this->session_expiring = $this->get_session_expiring( true ); + $this->set_session_cookie(); + $this->dirty = true; + $this->save(); + edd_debug_log( sprintf( 'Session expiration for %1$s was updated to: %2$s', $this->session_key, $this->session_expiry ) ); + } + } + + /** + * Saves the session data to the database and updates the cache. + * + * @param string $old_session_key The old session key. + * @return void + */ + public function save( $old_session_key = '' ) { + if ( ! $this->dirty ) { + return; + } + + if ( ! $this->is_request_valid_for_session() ) { + return; + } + + if ( ! $this->has_session() ) { + return; + } + + // If there is no session key, return. + if ( empty( $this->get_session_key() ) ) { + return; + } + + // Save the session. + $this->manager->save( $this->session_key, $this->data, $this->get_session_expiry() ); + + $this->dirty = false; + + // Delete the old session if it's a guest. + if ( $old_session_key && $this->should_delete( $old_session_key ) ) { + $this->manager->delete( $old_session_key ); + } + } + + /** + * Forget all session data without destroying it. + * + * @since 3.3.0 + */ + public function forget() { + \EDD\Utils\Cookies::set( $this->get_cookie_name() ); + $this->set_cart_cookie( false ); + + edd_empty_cart(); + + $this->data = array(); + $this->dirty = false; + $this->session_key = $this->get_session_key( true ); + } + + /** + * Destroys the session. + * + * @since 3.3.0 + */ + public function destroy() { + $this->manager->delete( $this->get_session_key() ); + $this->forget(); + } + + /** + * Resets the purchase session when an item is added to the cart. + * This ensures that the checkout is processed with fresh form data. + * + * @since 3.3.5 + */ + public function reset() { + $this->set( 'edd_purchase', null ); + } + + /** + * Get session data. + * + * @return array + */ + private function get_session_data() { + return $this->manager->get_session_data( $this->get_session_key() ); + } + + /** + * Get/set the session expiration. + * + * @since 3.3.0 + * @param bool $force Whether to force the expiration time to be set. + * @return int + */ + private function get_session_expiry( $force = false ) { + if ( ! $this->session_expiry || $force ) { + $session = $this->manager->get_session( $this->get_session_key() ); + if ( ! empty( $session->expiry ) ) { + $this->session_expiry = $session->expiry; + } else { + $this->session_expiry = time() + intval( $this->set_expiration_time() ); + } + } + + return $this->session_expiry; + } + + /** + * Get/set the session expiring (one hour before expiration). + * + * @since 3.3.0 + * @param bool $force Whether to force the expiration time to be set. + * @return int + */ + private function get_session_expiring( $force = false ) { + if ( ! $this->session_expiring || $force ) { + $this->session_expiring = $this->get_session_expiry() - HOUR_IN_SECONDS; + } + + return $this->session_expiring; + } + + /** + * Generate a unique session ID for guests, or return user ID if logged in. + * Uses Portable PHP password hashing framework to generate a unique cryptographically strong ID. + * Legacy method (name). + * + * @since 3.3.0 + * @return string + */ + private function get_session_key( $reset = false ) { + if ( ! is_null( $this->session_key ) && ! $reset ) { + return $this->session_key; + } + + if ( is_user_logged_in() ) { + $session_key = $this->get_logged_in_user_key(); + } + + if ( ! empty( $session_key ) ) { + $this->session_key = $session_key; + } else { + + require_once ABSPATH . 'wp-includes/class-phpass.php'; + $hasher = new \PasswordHash( 8, false ); + + $this->session_key = $this->guest_prefix . substr( wp_hash( $hasher->get_random_bytes( 32 ) ), 2 ); + } + + return $this->session_key; + } + + /** + * Gets the logged in user key. + * + * @since 3.3.0 + * @return string + */ + private function get_logged_in_user_key() { + return $this->manager->get_logged_in_user_key(); + } + + /** + * Checks if the current session should be deleted. + * + * @param string $key The session key. + * @return bool + */ + private function should_delete( $key ) { + $current_user_id = $this->get_logged_in_user_key(); + + return $current_user_id !== $key && ! ( get_user_by( 'id', $key ) instanceof \WP_User ); + } + + /** + * Sanitizes sensitive data from the session array. + * + * @param mixed $data The data to sanitize. + * @return mixed + */ + private function sanitize( $data ) { + if ( ! is_array( $data ) ) { + return esc_attr( $data ); + } + $disallowed_keys = array( + 'post_data', + 'card_info', + ); + foreach ( $disallowed_keys as $key ) { + if ( isset( $data[ $key ] ) ) { + unset( $data[ $key ] ); + } + } + + return $data; + } + + /** + * Checks whether the type of request is valid for starting a session. + * + * @since 3.3.0 + * @return bool + */ + private function is_request_valid_for_session() { + return \EDD\Utils\Request::is_request( array( 'frontend', 'rest' ) ); + } +} diff --git a/src/Sessions/Managers/Database.php b/src/Sessions/Managers/Database.php new file mode 100644 index 00000000000..1426a5c364a --- /dev/null +++ b/src/Sessions/Managers/Database.php @@ -0,0 +1,181 @@ +get_session( $session_key ); + + return $session instanceof Session ? $session->session_value : array(); + } + + /** + * Returns the session. + * + * @since 3.3.0 + * @param string $session_key Customer ID. + * @return Session + */ + public function get_session( $session_key ) { + + if ( is_null( $this->session ) ) { + $session = $this->get_by_key( $session_key ); + + if ( ! $session ) { + $session = new Session(); + } + + $this->session = $session; + } + + return $this->session; + } + + /** + * Gets the logged in user key. + * + * @since 3.3.0 + * @return string + */ + public function get_logged_in_user_key() { + return strval( get_current_user_id() ); + } + + /** + * Saves the session data to the database and updates the cache. + * + * @param string $session_key The session key. + * @param array $data The data to save. + * @return void + */ + public function save( string $session_key, array $data, int $session_expiry ) { + $data = array( + 'session_key' => $session_key, + 'session_value' => $data, + 'session_expiry' => $session_expiry, + ); + + // Add the session to the database. + $this->add( $data ); + } + + /** + * Adds our custom cron schedule. + * + * @since 3.3.0 + * + * @param array $schedules The existing cron schedules. + * + * @return array + */ + public function add_cron_schedule( $schedules ) { + $schedules[] = new Cron\Schedules\SessionCleanup(); + + return $schedules; + } + + /** + * Adds our custom cron event. + * + * @since 3.3.0 + * + * @param array $events The existing cron events. + * + * @return array + */ + public function add_cron_event( $events ) { + $events[] = new Cron\Events\SessionCleanup(); + + return $events; + } + + /** + * Adds our custom cron component. + * + * @since 3.3.0 + * + * @param array $components The existing cron components. + * + * @return array + */ + public function add_cron_component( $components ) { + $components[] = new Cron\Components\Sessions(); + + return $components; + } + + /** + * Registers the session component. + * + * @since 3.3.0 + * @return void + */ + public function setup_sessions() { + edd_register_component( + 'session', + array( + 'schema' => '\\EDD\\Database\\Schemas\\Sessions', + 'table' => '\\EDD\\Database\\Tables\\Sessions', + 'query' => '\\EDD\\Database\\Queries\\Session', + 'object' => '\\EDD\\Sessions\\Session', + 'meta' => false, + ) + ); + } + + /** + * Determines if we should start the session. + * This is used to prevent the session from starting before the EDD database components are loaded. + * + * @since 3.3.4 + * @param bool $should_start Whether we should start the session. + * @return bool + */ + public function should_start_session( $should_start ) { + if ( ! did_action( 'edd_setup_components' ) ) { + return false; + } + + return $should_start; + } +} diff --git a/src/Sessions/Managers/Manager.php b/src/Sessions/Managers/Manager.php new file mode 100644 index 00000000000..06ab34653c6 --- /dev/null +++ b/src/Sessions/Managers/Manager.php @@ -0,0 +1,54 @@ +get_by_key( $data['session_key'] ); + if ( empty( $data['session_value'] ) ) { + if ( $exists ) { + return $this->delete( $data['session_key'] ); + } + + return false; + } + + $data['session_value'] = maybe_serialize( $data['session_value'] ); + + if ( $exists ) { + return $this->update( $exists->session_id, $data ); + } + + $query = new DB(); + + return $query->add_item( $data ); + } + + /** + * Updates a session in the database. + * + * @since 3.3.0 + * @param int $session_id The ID of the session to update. + * @param array $data The array of data to add. + * @return bool + */ + protected function update( $session_id, $data = array() ) { + if ( empty( $session_id ) || empty( $data ) ) { + return false; + } + $query = new DB(); + + return $query->update_item( $session_id, $data ); + } + + /** + * Gets a session from the database. + * + * @since 3.3.0 + * @param string $session_key The value to query by. + * @return null|EDD\Sessions\Session + */ + protected function get_by_key( $session_key ) { + // This is necessary because Auto Register looks for this before the component is registered. + if ( ! edd_get_component( 'session' ) || empty( $session_key ) ) { + return false; + } + $query = new DB(); + + return $query->get_item_by( 'session_key', $session_key ); + } + + /** + * Deletes the session from the database. + * + * @since 3.3.0 + * @param string $session_key The session key to delete. + * @return bool + */ + public function delete( $session_key ) { + $session_id = $this->get_by_key( $session_key ); + $query = new DB(); + + return $query->delete_item( $session_id ); + } +} diff --git a/src/Sessions/PurchaseData.php b/src/Sessions/PurchaseData.php new file mode 100644 index 00000000000..fd49fd999e3 --- /dev/null +++ b/src/Sessions/PurchaseData.php @@ -0,0 +1,242 @@ + 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' => self::get_tax_rate( $valid_data ), + 'price' => edd_get_cart_total(), // Amount after taxes. + 'user_email' => $user_info['email'], + 'purchase_key' => self::get_purchase_key( $user_info['email'] ), + 'user_info' => stripslashes_deep( $user_info ), + 'gateway' => $valid_data['gateway'], + 'cart_details' => edd_get_cart_content_details(), + 'date' => false, // unused key, but kept for backwards compatibility. + ); + + // Add the user data for hooks. + $valid_data['user'] = $user; + + /** + * Allow themes and plugins to hook to the purchase data. + * + * @param array $post_data The POST data. + * @param array $user_info The user information. + * @param array $valid_data The valid data. + */ + do_action( 'edd_checkout_before_gateway', $_POST, $user_info, $valid_data ); + + $purchase_data['gateway_nonce'] = wp_create_nonce( 'edd-gateway' ); + + /** + * Allow the purchase data to be modified before it is sent to the gateway. + * + * @param array $purchase_data The purchase data. + * @param array $valid_data The valid data. + */ + $purchase_data = apply_filters( + 'edd_purchase_data_before_gateway', + $purchase_data, + $valid_data + ); + + if ( empty( $purchase_data['price'] ) ) { + // Revert to manual. + $purchase_data['gateway'] = 'manual'; + $_POST['edd-gateway'] = 'manual'; + } + + edd_set_purchase_session( $purchase_data ); + + // Send the card info and post data back to the purchase data, even though it's not stored in the session. + $purchase_data['card_info'] = $valid_data['cc_info'] ?? array(); + $purchase_data['post_data'] = $_POST; + + return $purchase_data; + } + + /** + * Retrieves the purchase data from the session, or attampts to generate it if it does not exist. + * + * @since 3.3.2 + * @param bool|null $doing_ajax Whether the request is being made via AJAX. + * @return mixed The purchase data. + */ + public static function get( $doing_ajax = null ) { + $session = edd_get_purchase_session(); + if ( ! empty( $session ) ) { + return $session; + } + + // Generally we expected the purchase data to be in the session, but if not, log it. + edd_debug_log( 'Purchase data not found in session. Attempting to generate it.' ); + + return self::start( $doing_ajax ); + } + + /** + * Retrieves the tax rate for the purchase data. + * + * @since 3.3.2 + * @param array $valid_data The valid data for the purchase. + * @return float The tax rate for the purchase data. + */ + private static function get_tax_rate( $valid_data ) { + if ( ! edd_use_taxes() ) { + return 0; + } + + $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; + + return edd_get_cart_tax_rate( $card_country, $card_state, $card_zip ); + } + + /** + * Retrieves the purchase key for an order. + * + * @since 3.3.2 + * @param string $email The email address for the user. + * @return string The purchase key for the user. + */ + private static function get_purchase_key( $email ) { + $existing_payment = EDD()->session->get( 'edd_resume_payment' ); + if ( ! empty( $existing_payment ) ) { + $order = edd_get_order( $existing_payment ); + if ( ! empty( $order->payment_key ) && $order->is_recoverable() ) { + return $order->payment_key; + } + } + + return edd_generate_order_payment_key( $email ); + } + + /** + * Retrieves user information based on the provided valid data and user object. + * + * @since 3.3.2 + * @param array $valid_data The valid data for the user. + * @param array $user The user data. + * @return array + */ + private static function get_user_info( $valid_data, $user ) { + return array( + 'id' => ! empty( $user['user_id'] ) ? $user['user_id'] : false, + 'email' => ! empty( $user['user_email'] ) ? $user['user_email'] : '', + 'first_name' => ! empty( $user['user_first'] ) ? $user['user_first'] : '', + 'last_name' => ! empty( $user['user_last'] ) ? $user['user_last'] : '', + 'discount' => ! empty( $valid_data['discount'] ) ? $valid_data['discount'] : false, + 'address' => ! empty( $user['address'] ) ? $user['address'] : false, + ); + } + + /** + * Updates the customer record if the user has added or updated information. + * + * @since 3.3.5 + * @param array $user_info The user information. + */ + private static function maybe_update_customer( $user_info ) { + $customer = edd_get_customer_by( 'email', $user_info['email'] ); + if ( ! $customer ) { + return; + } + $name = trim( $user_info['first_name'] . ' ' . $user_info['last_name'] ); + if ( empty( $customer->name ) || $name !== $customer->name ) { + $customer->update( + array( + 'name' => $name, + ) + ); + } + + if ( empty( $user_info['address'] ) ) { + return; + } + + $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'], + ); + + edd_maybe_add_customer_address( $customer->id, $address ); + } +} diff --git a/src/Sessions/Session.php b/src/Sessions/Session.php new file mode 100644 index 00000000000..a421da5908a --- /dev/null +++ b/src/Sessions/Session.php @@ -0,0 +1,63 @@ +session_value; + if ( empty( $session_value ) ) { + return array(); + } + + return maybe_unserialize( $session_value ); + } +} diff --git a/src/Sessions/Traits/Cookie.php b/src/Sessions/Traits/Cookie.php new file mode 100644 index 00000000000..f801005432f --- /dev/null +++ b/src/Sessions/Traits/Cookie.php @@ -0,0 +1,200 @@ +get_cookie_name(); + $cookie_value = isset( $_COOKIE[ $cookie_name ] ) ? wp_unslash( $_COOKIE[ $cookie_name ] ) : false; + if ( empty( $cookie_value ) || ! is_string( $cookie_value ) ) { + return false; + } + + $parsed_cookie = explode( '||', $cookie_value ); + if ( count( $parsed_cookie ) < 4 ) { + return false; + } + + list( $session_key, $session_expiration, $session_expiring, $cookie_hash ) = $parsed_cookie; + + if ( empty( $session_key ) ) { + edd_debug_log( 'Session key is missing.' ); + return false; + } + + // Validate hash. + $to_hash = $session_key . '|' . $session_expiration; + $hash = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) ); + + if ( empty( $cookie_hash ) || ! hash_equals( $hash, $cookie_hash ) ) { + edd_debug_log( 'Session cookie hash mismatch for: ' . $session_key ); + return false; + } + + return array( + 'session_key' => $session_key, + 'session_expiration' => $session_expiration, + 'session_expiring' => $session_expiring, + ); + } + + /** + * Sets the session cookie. + * + * @since 3.3.0 + * @return void + */ + protected function set_session_cookie() { + if ( empty( $this->session_key ) ) { + $this->get_session_key(); + } + $expiry = $this->get_session_expiry(); + $expiring = $this->get_session_expiring(); + $to_hash = $this->session_key . '|' . $expiry; + $cookie_hash = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) ); + $cookie_value = $this->session_key . '||' . $expiry . '||' . $expiring . '||' . $cookie_hash; + $this->has_cookie = true; + + edd_debug_log( 'Setting session cookie for: ' . $this->session_key ); + \EDD\Utils\Cookies::set( $this->get_cookie_name(), $cookie_value, $expiry ); + } + + /** + * 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. + * Legacy method. + * + * @param bool $set Whether to set the cookie. False will set an expired cookie. + * @return bool + */ + public function set_cart_cookie( $set = true ) { + if ( ! $this->use_cart_cookie() ) { + return false; + } + + if ( ! $set ) { + return \EDD\Utils\Cookies::set( 'edd_items_in_cart' ); + } + + return \EDD\Utils\Cookies::set( 'edd_items_in_cart', '1', time() + 30 * MINUTE_IN_SECONDS ); + } + + /** + * Gets the cookie name. + * + * @since 3.3.4 + * @return string + */ + protected function get_cookie_name() { + if ( $this->cookie ) { + return $this->cookie; + } + + $this->cookie = $this->get_cookie_prefix() . 'edd_session_' . COOKIEHASH; + + return $this->cookie; + } + + /** + * Checks if session cookie is expired, or belongs to a logged out user. + * + * @since 3.3.0 + * @return bool Whether session cookie is valid. + */ + private function is_session_cookie_valid() { + // If session is expired, session cookie is invalid. + if ( time() > $this->get_session_expiry() ) { + return false; + } + + // If user has logged out, session cookie is invalid. + if ( ! is_user_logged_in() && ! $this->is_guest( $this->session_key ) ) { + return false; + } + + // Session from a different user is not valid. (Although from a guest user will be valid). + if ( + is_user_logged_in() && + ! $this->is_guest( $this->session_key ) && + $this->get_logged_in_user_key() !== $this->session_key + ) { + return false; + } + + return true; + } + + /** + * Checks if this is an auto-generated customer ID. + * + * @since 3.3.0 + * @param string|int $session_key Customer ID to check. + * @return bool Whether customer ID is randomly generated. + */ + private function is_guest( $session_key ) { + $session_key = strval( $session_key ); + + if ( empty( $session_key ) ) { + return true; + } + + return substr( $session_key, 0, 2 ) === $this->guest_prefix; + } + + /** + * Return true if the current user has an active session, i.e. a cookie to retrieve values. + * + * @return bool + */ + private function has_session() { + return isset( $_COOKIE[ $this->get_cookie_name() ] ) || $this->has_cookie || is_user_logged_in(); // @codingStandardsIgnoreLine. + } + + /** + * Gets the cookie prefix. + * + * @since 3.3.4 + * @return string + */ + private function get_cookie_prefix() { + /** + * Allow developers to filter the session cookie prefix. + * + * By default the prefix is an empty string. + * + * @since 3.3.4 + * @param string $prefix The session cookie prefix. + */ + return apply_filters( 'edd_session_cookie_prefix', '' ); + } +} diff --git a/src/Sessions/Traits/Legacy.php b/src/Sessions/Traits/Legacy.php new file mode 100644 index 00000000000..0b257004ca1 --- /dev/null +++ b/src/Sessions/Traits/Legacy.php @@ -0,0 +1,167 @@ +is_request_valid_for_session() ) { + return false; + } + if ( isset( $_COOKIE['edd_items_in_cart'] ) ) { + return true; + } + if ( ! empty( $_SERVER['REQUEST_URI'] ) ) { + $blacklist = $this->get_blacklist(); + $uri = ltrim( $_SERVER['REQUEST_URI'], '/' ); + $uri = untrailingslashit( $uri ); + + if ( in_array( $uri, $blacklist, true ) ) { + return false; + } + + if ( false !== strpos( $uri, 'feed=' ) ) { + return 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' ) ) { + return false; + } + + // Starting sessions while saving the file editor can break the save process, so don't start. + if ( false !== strpos( $uri, 'wp_scrape_key' ) ) { + return false; + } + } + + return true; + } + + /** + * Retrieve the URI blacklist. + * + * These are the URIs where we never start sessions. + * Legacy method. + * + * @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; + } + + /** + * Whether to use PHP sessions. + * Legacy method. + * + * @return bool + */ + public function use_php_sessions() { + $session_handling = get_option( 'edd_session_handling', false ); + if ( ! empty( $session_handling ) ) { + return 'php' === $session_handling; + } + + $use_php_sessions = (bool) get_option( 'edd_use_php_sessions', false ); + + if ( ! $use_php_sessions && function_exists( 'session_start' ) ) { + if ( ! headers_sent() ) { + session_start(); + } + // Attempt to detect if the server supports PHP sessions. + $_SESSION['edd_use_php_sessions'] = true; + if ( ! empty( $_SESSION['edd_use_php_sessions'] ) ) { + $use_php_sessions = true; + } + } + + // Enable or disable PHP Sessions based on the EDD_USE_PHP_SESSIONS constant. + if ( defined( 'EDD_USE_PHP_SESSIONS' ) ) { + $use_php_sessions = (bool) EDD_USE_PHP_SESSIONS; + } + + $use_php_sessions = apply_filters( 'edd_use_php_sessions', $use_php_sessions ); + + update_option( 'edd_session_handling', $use_php_sessions ? 'php' : 'db' ); + delete_option( 'edd_use_php_sessions' ); + + return $use_php_sessions; + } + + /** + * Whether to use the cart cookie. + * Legacy method. + * + * @deprecated 3.3.0 Left for backwards compatibility. + * @return bool + */ + public function use_cart_cookie() { + $use_cart_cookie = true; + if ( defined( 'EDD_USE_CART_COOKIE' ) && ! EDD_USE_CART_COOKIE ) { + $use_cart_cookie = false; + } + + return (bool) apply_filters( 'edd_use_cart_cookie', $use_cart_cookie ); + } + + /** + * Initialize the session handler. + * Legacy method. + * + * @since 3.3.0 + * @return void + */ + public function init() {} +} diff --git a/src/Settings/Sanitize/Types/RichEditor.php b/src/Settings/Sanitize/Types/RichEditor.php new file mode 100644 index 00000000000..a68f8657f24 --- /dev/null +++ b/src/Settings/Sanitize/Types/RichEditor.php @@ -0,0 +1,44 @@ +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..0898eb4e371 --- /dev/null +++ b/src/Telemetry/Data.php @@ -0,0 +1,53 @@ + $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(), + 'products' => new Products(), + ); + + foreach ( $classes as $key => $class ) { + $data[ $key ] = $class->get(); + } + + return $data; + } +} diff --git a/src/Telemetry/Environment.php b/src/Telemetry/Environment.php new file mode 100644 index 00000000000..ecedf17144c --- /dev/null +++ b/src/Telemetry/Environment.php @@ -0,0 +1,144 @@ + phpversion(), + 'php_arch' => 4 === PHP_INT_SIZE ? '32' : '64', + '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(), + 'is_ssl' => (int) (bool) is_ssl(), + 'stripe_connect' => (int) (bool) edd_get_option( 'stripe_connect_account_id' ), + 'rest_enabled' => (int) (bool) $this->is_rest_enabled(), + ); + + $server = $this->parse_server(); + $multisite = $this->parse_multisite(); + + return array_merge( $data, $server, $multisite ); + } + + /** + * 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; + } + + /** + * Adds the multisite data to the array of data. + * + * @since 3.3.0 + * @return array + */ + private function parse_multisite() { + $data = array( + 'multisite' => (int) (bool) is_multisite(), + ); + + if ( is_multisite() ) { + $data['multisite_mode'] = defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ? 'subdomain' : 'subdirectory'; + $data['network_activated'] = (int) (bool) is_plugin_active_for_network( EDD_PLUGIN_BASE ); + + $sites = get_sites(); + $data['network_sites'] = count( $sites ); + + $domains = wp_list_pluck( $sites, 'domain' ); + + $data['domain_mapping'] = count( array_unique( $domains ) ) > 1 ? 1 : 0; + + $main_site = is_main_site(); + $data['is_main_site'] = $main_site ? 1 : 0; + + if ( empty( $main_site ) ) { + $main_site_uuid = get_blog_option( get_main_site_id(), 'edd_telemetry_uuid', 0 ); + $data['main_site_id'] = $main_site_uuid; + } + } + + 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 $this->anonymize_site_name( $active_theme->name ); + } + + /** + * Test if the REST API is accessible. + * + * The REST API might be inaccessible due to various security measures, + * or it might be completely disabled by a plugin. + * + * @since 3.2.0 + * @return bool + */ + private function is_rest_enabled() { + $checker = new \EDD\Utils\RESTChecker( 'wp/v2/edd-downloads' ); + + return $checker->is_enabled(); + } +} diff --git a/src/Telemetry/Integrations.php b/src/Telemetry/Integrations.php new file mode 100644 index 00000000000..be12778bd00 --- /dev/null +++ b/src/Telemetry/Integrations.php @@ -0,0 +1,93 @@ +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 The plugin basename. + * @param array $details The plugin 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 The plugin basename. + * @param array $details The plugin 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..88ba4c431ad --- /dev/null +++ b/src/Telemetry/Licenses.php @@ -0,0 +1,97 @@ +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 The license object. + * @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..0587e415be5 --- /dev/null +++ b/src/Telemetry/Orders.php @@ -0,0 +1,187 @@ +type = $type; + } + + /** + * Gets the gateway data. + * + * @return array + */ + public function get() { + /** + * Allows the ability to opt-out of sending aggregate gateway data to the telemetry server. + * + * @since 3.2.8 + * + * @param bool $send_aggregate_data Whether to send aggregate gateway data to the telemetry server. Default is true. + * @return bool + */ + if ( false === apply_filters( 'edd_telemetry_send_aggregate_gateway_data', true ) ) { + return array(); + } + + $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/Products.php b/src/Telemetry/Products.php new file mode 100644 index 00000000000..57d205daaae --- /dev/null +++ b/src/Telemetry/Products.php @@ -0,0 +1,106 @@ + $this->get_product_count( + array( + 'key' => '_variable_pricing', + 'value' => 1, + 'compare' => '=', + ) + ), + 'hidden_purchase_link' => $this->get_product_count( + array( + 'key' => '_edd_hide_purchase_link', + 'compare' => 'EXISTS', + ) + ), + 'download_limit' => $this->get_product_count( + array( + 'key' => '_edd_download_limit', + 'value' => 0, + 'compare' => '>', + ) + ), + 'quantities_disabled' => $this->get_product_count( + array( + 'key' => '_edd_quantities_disabled', + 'compare' => 'EXISTS', + ) + ), + ); + + foreach ( edd_get_download_types() as $slug => $label ) { + $query = array( + 'key' => '_edd_product_type', + ); + + if ( empty( $slug ) ) { + $query['compare'] = 'NOT EXISTS'; + } else { + $query['value'] = $slug; + $query['compare'] = '='; + } + + $slug = empty( $slug ) ? 'default' : $slug; + $data[ 'download_type_' . $slug ] = $this->get_product_count( $query ); + } + + /** + * Filters the product data to send to the telemetry server. + * Values should be: + * - key: a unique string to identify the product/meta + * - value: the value of the product/meta. This can be a string, int, or array. + */ + return apply_filters( 'edd_telemetry_products', $data ); + } + + /** + * Gets the number of variable products. + * + * @since 3.2.0 + * @return int + */ + private function get_product_count( $meta_query ) { + + $query = new \WP_Query( + array( + 'post_type' => 'download', + 'status' => 'publish', + 'nopaging' => true, + 'meta_query' => array( + $meta_query, + ), + ) + ); + + return absint( $query->post_count ); + } +} diff --git a/src/Telemetry/Settings.php b/src/Telemetry/Settings.php new file mode 100644 index 00000000000..9fa333632dc --- /dev/null +++ b/src/Telemetry/Settings.php @@ -0,0 +1,251 @@ + $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; + } + } + } + } + } + + $emails = $this->get_registered_emails(); + if ( $emails ) { + $data = array_merge( $data, $emails ); + } + + return $data; + } + + /** + * Gets the id and value for an individual setting. + * + * @param string $tab_key The tab key. + * @param string $section_key The section key. + * @param string $setting_key The 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 ) ) { + if ( 'currency' === $setting['id'] ) { + return edd_get_currency(); + } + if ( 'base_country' === $setting['id'] ) { + return strtoupper( edd_get_option( 'stripe_connect_account_country', $value ) ); + } + } + if ( in_array( $setting['type'], $this->text_settings(), true ) ) { + return $this->anonymize( $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 The setting definition. + * @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', + 'password', + ) + ); + } + + /** + * 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 The setting definition. + * @return bool + */ + private function should_populate_array( $setting ) { + $settings = array( 'gateways', 'accepted_cards' ); + + return 'multicheck' === $setting['type'] || in_array( $setting['id'], $settings, true ); + } + + /** + * 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; + } + + /** + * Gets the array of registered email templates. + * + * @since 3.3.0 + * @return array + */ + private function get_registered_emails() { + $registry = edd_get_email_registry(); + $data = array(); + foreach ( $registry->get_emails() as $key => $email_class ) { + $email = $registry->make_email_class( $email_class, array( $key ) ); + if ( $email->can_view ) { + $data[ "email_template_{$email->email_id}" ] = (int) (bool) $email->status; + } + } + + return $data; + } +} diff --git a/src/Telemetry/Stats.php b/src/Telemetry/Stats.php new file mode 100644 index 00000000000..5339804acd0 --- /dev/null +++ b/src/Telemetry/Stats.php @@ -0,0 +1,156 @@ + $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(), + 'categories' => $this->get_category_count(), + 'tags' => $this->get_tag_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 The timestamp to convert. + * @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; + } + + /** + * Gets the number of product categories on the website. + * + * @since 3.2.10 + * @return int + */ + private function get_category_count() { + return wp_count_terms( 'download_category' ); + } + + /** + * Gets the number of product tags on the website. + * + * @since 3.2.10 + * @return int + */ + private function get_tag_count() { + return wp_count_terms( 'download_tag' ); + } +} diff --git a/src/Telemetry/Tracking.php b/src/Telemetry/Tracking.php new file mode 100644 index 00000000000..d1957a8640f --- /dev/null +++ b/src/Telemetry/Tracking.php @@ -0,0 +1,351 @@ +data = $data->get(); + } + + /** + * Send the data to the EDD server + * + * @access private + * + * @param bool $override If we should override the tracking setting. + * @param bool $ignore_last_checkin If we should ignore when the last check in was. + * + * @return bool + */ + public function send_checkin( $override = false, $ignore_last_checkin = false ) { + + if ( ! $this->can_send_data( $override, $ignore_last_checkin ) ) { + return false; + } + + $this->setup_data(); + + if ( empty( $this->data ) ) { + return false; + } + + new \EDD\Utils\RemoteRequest( + $this->telemetry_server, + array( + 'method' => 'POST', + 'timeout' => 8, + 'redirection' => 5, + 'httpversion' => '1.1', + 'blocking' => false, + 'body' => $this->data, + 'user-agent' => 'EDD/' . EDD_VERSION . '; ' . $this->data['id'], + ) + ); + + update_option( 'edd_tracking_last_send', time(), false ); + + return true; + } + + /** + * Check for a new opt-in on settings save. + * This runs during the sanitation of General settings, thus the return + * + * @return array + */ + public function check_for_settings_optin( $input ) { + + if ( isset( $input['allow_tracking'] ) && 1 === absint( $input['allow_tracking'] ) ) { + $this->send_checkin( true ); + } + + return $input; + } + + /** + * Adds the tracking setting to the miscellaneous settings section. + * + * @since 3.1.1 + * @param array $settings The settings array. + * @return array + */ + public function register_setting( $settings ) { + $hidden = edd_get_option( 'allow_tracking', false ) ? '' : 'edd-hidden'; + + $settings['main']['allow_tracking'] = array( + 'id' => 'allow_tracking', + 'name' => __( 'Join the EDD Community', 'easy-digital-downloads' ), + 'check' => __( 'Yes, I want to help!', 'easy-digital-downloads' ) . ' ', + 'desc' => $this->get_telemetry_description(), + 'type' => 'checkbox_description', + ); + + return $settings; + } + + /** + * Check for a new opt-in via the admin notice + * + * @return void + */ + public function check_for_optin() { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'edd_optin' ) ) { + return; + } + + edd_update_option( 'allow_tracking', 1 ); + $this->send_checkin( true ); + update_option( 'edd_tracking_notice', 1, false ); + } + + /** + * Check for a new opt-out via the admin notice + * + * @return void + */ + public function check_for_optout() { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'edd_optout' ) ) { + return; + } + + edd_delete_option( 'allow_tracking' ); + update_option( 'edd_tracking_notice', 1, false ); + edd_redirect( remove_query_arg( 'edd_action' ) ); + } + + /** + * Get the last time a checkin was sent + * + * @access private + * @return false|string + */ + private function get_last_send() { + return get_option( 'edd_tracking_last_send' ); + } + + /** + * Schedule a weekly checkin + * + * We send once a week (while tracking is allowed) to check in, which can be + * used to determine active sites. + * + * @return void + */ + public function schedule_send() {} + + /** + * Display the admin notice to users that have not opted-in or out + * + * @return void + */ + public function admin_notice() { + static $once = null; + + // Only 1 notice. + if ( ! is_null( $once ) ) { + return; + } + + // Already ran once. + $once = true; + + // Bail if already noticed. + if ( get_option( 'edd_tracking_notice' ) ) { + return; + } + + // Bail if already allowed. + if ( edd_get_option( 'allow_tracking', false ) ) { + return; + } + + // Bail if user cannot decide. + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + // No notices for local installs. + if ( edd_is_dev_environment() ) { + update_option( 'edd_tracking_notice', 1, false ); + return; + } + + if ( edd_is_admin_page() && ! edd_is_admin_page( 'index.php' ) && ! edd_is_insertable_admin_page() ) { + + // Add the notice. + EDD()->notices->add_notice( + array( + 'id' => 'edd-allow-tracking', + 'class' => 'updated', + 'message' => $this->get_admin_notice_message(), + 'is_dismissible' => false, + ) + ); + } + } + + /** + * Build the admin notice message. + * + * @since 3.1.1 + * @return array + */ + private function get_admin_notice_message() { + + return array( + '' . __( 'Join the EDD Community', 'easy-digital-downloads' ) . '', + $this->get_telemetry_description(), + sprintf( + '%s %s', + esc_url( wp_nonce_url( add_query_arg( 'edd_action', 'opt_into_tracking' ), 'edd_optin' ) ), + __( 'Allow', 'easy-digital-downloads' ), + esc_url( wp_nonce_url( add_query_arg( 'edd_action', 'opt_out_of_tracking' ), 'edd_optout' ) ), + __( 'Do not allow', 'easy-digital-downloads' ) + ), + ); + } + + /** + * Gets the telemetry description. + * + * @since 3.1.1 + * @return string + */ + public function get_telemetry_description() { + + return __( 'Help us provide a better experience and faster fixes by sharing some anonymous data about how you use Easy Digital Downloads.', 'easy-digital-downloads' ) . + ' ' . + sprintf( + /* translators: %1$s Link to tracking information, do not translate. %2$s clsoing link tag, do not translate */ + __( '%1$sHere is what we track.%2$s', 'easy-digital-downloads' ), + '', + '' + ); + } + + /** + * Whether we can send the data. + * + * @since 3.1.1 + * @param bool $override If we should override the tracking setting. + * @param bool $ignore_last_checkin If we should ignore when the last check in was. + * @return bool + */ + public function can_send_data( $override, $ignore_last_checkin ) { + + if ( ! $this->environment_allows_tracking() ) { + return false; + } + + if ( ! $this->tracking_allowed() && ! $override ) { + return false; + } + + // Send a maximum of once per week. + $last_send = $this->get_last_send(); + if ( ! $ignore_last_checkin && is_numeric( $last_send ) && $last_send > strtotime( '-1 week' ) ) { + return false; + } + + return true; + } + + /** + * Whether the environment allows sending telemetry data. + * + * @since 3.2.7 + * @return bool + */ + private function environment_allows_tracking() { + + if ( edd_is_dev_environment() || edd_is_test_mode() ) { + return false; + } + + if ( 'staging' === wp_get_environment_type() ) { + return false; + } + + return true; + } +} diff --git a/src/Telemetry/Traits/Anonymize.php b/src/Telemetry/Traits/Anonymize.php new file mode 100644 index 00000000000..e369b776bf3 --- /dev/null +++ b/src/Telemetry/Traits/Anonymize.php @@ -0,0 +1,85 @@ +anonymize_site_name( $value ); + } + + /** + * Replace any use of the site name in a string. + * + * @since 3.2.5 + * @param string $value The string to anonymize. + * @return string + */ + private function anonymize_site_name( $value ) { + $site_name = get_bloginfo( 'name' ); + if ( false === strpos( $value, $site_name ) ) { + return $value; + } + + return str_replace( $site_name, edd_mask_string( $site_name ), $value ); + } + + /** + * 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 + * @since 3.3.0 Moved to the Anonymize trait, to modularize the information. + * @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/Upgrades/Adjustments/DiscountsStartEnd.php b/src/Upgrades/Adjustments/DiscountsStartEnd.php new file mode 100644 index 00000000000..bf5d1f13cb9 --- /dev/null +++ b/src/Upgrades/Adjustments/DiscountsStartEnd.php @@ -0,0 +1,331 @@ + 'process_step', + ); + + if ( ! SingleEvent::next_scheduled( self::$cron_action ) ) { + $hooks['shutdown'] = array( 'maybe_schedule_background_update', 99 ); + } + + return $hooks; + } + + /** + * Maybe schedule the background update. + * + * @since 3.2.10 + * @return void + */ + public function maybe_schedule_background_update() { + // If we've already scheduled the cleanup, no need to schedule it again. + if ( SingleEvent::next_scheduled( self::$cron_action ) ) { + return; + } + + // See if we have any adjustments with 0000-00-00 00:00:00 dates for the start and end. + $affected_discount_count = $this->get_discounts( true ); + + if ( empty( $affected_discount_count ) ) { + $this->mark_complete( false ); + return; + } + + // Only update the total count option if it doesn't exist. This prevents it from being overridden in some edge cases. + if ( empty( get_option( $this->total_count_option ) ) ) { + // Set the total amount in a transient so we can use it later. + update_option( $this->total_count_option, $affected_discount_count, false ); + } + + $this->add_or_update_initial_notification(); + + // ...And schedule a single event a minute from now to start the processing of this data. + SingleEvent::add( + time() + MINUTE_IN_SECONDS, + self::$cron_action + ); + } + + /** + * Process the upgrade step. + * + * @since 3.2.10 + * @return void + */ + public function process_step() { + // Only let this run in the background. + if ( ! edd_doing_cron() ) { + return; + } + + $discounts = $this->get_discounts(); + if ( empty( $discounts ) ) { + $this->mark_complete( true ); + return; + } + + foreach ( $discounts as $discount ) { + $data = array(); + if ( $this->zeroed_value === $discount->start_date ) { + $data['start_date'] = null; + } + if ( $this->zeroed_value === $discount->end_date ) { + $data['end_date'] = null; + } + if ( ! empty( $data ) ) { + edd_update_adjustment( $discount->id, $data ); + } + } + + $this->add_or_update_initial_notification(); + + // Schedule the next step. + SingleEvent::add( + time() + MINUTE_IN_SECONDS, + self::$cron_action + ); + } + + /** + * Marks the fix process as complete. + * + * @since 3.2.10 + * + * @param bool $add_notification If we should add a notification that the fix process is complete. + * + * @return void + */ + private function mark_complete( $add_notification = true ) { + // Set the upgrade as complete. + edd_set_upgrade_complete( self::$upgrade_name ); + + // Delete the total count option. It may not exist, but we should delete it anyway. + delete_option( $this->total_count_option ); + + if ( false === $add_notification ) { + return; + } + + $initial_notification = $this->get_initial_notification(); + if ( ! empty( $initial_notification ) ) { + EDD()->notifications->update( $initial_notification->id, array( 'dismissed' => 1 ) ); + } + + EDD()->notifications->maybe_add_local_notification( + array( + 'remote_id' => $this->complete_remote_id, + 'buttons' => '', + 'conditions' => '', + 'type' => 'success', + 'title' => __( 'Discount Updates Complete!', 'easy-digital-downloads' ), + 'content' => __( 'Easy Digital Downloads has finished updating your discount codes! Thank you for your patience.', 'easy-digital-downloads' ), + ) + ); + } + + /** + * Gets the initial notification about the migration. + * + * @since 3.2.10 + * + * @return object + */ + private function get_initial_notification() { + return EDD()->notifications->get_item_by( 'remote_id', $this->in_progress_remote_id ); + } + + /** + * Adds or updates the initial notification about the migration. + * + * @since 3.2.10 + * + * @return void + */ + private function add_or_update_initial_notification() { + $initial_notification = $this->get_initial_notification(); + $percent_complete = $this->get_percentage_complete(); + + /* translators: %s: % complete. */ + $notification_title = sprintf( __( 'Updating Discounts ( %d%% )', 'easy-digital-downloads' ), $percent_complete ); + + if ( ! empty( $initial_notification ) ) { + $date = new Date(); + $date->setTimestamp( time() )->setTimezone( new \DateTimeZone( 'UTC' ) ); + + // Update the notification. + EDD()->notifications->update( + $initial_notification->id, + array( + 'title' => $notification_title, + 'date_created' => $date->format( 'mysql' ), + ) + ); + } else { + // Add the notification. + EDD()->notifications->maybe_add_local_notification( + array( + 'remote_id' => $this->in_progress_remote_id, + 'buttons' => '', + 'conditions' => '', + 'type' => 'info', + 'title' => $notification_title, + 'content' => __( 'Easy Digital Downloads is performing maintenance in the background on discount codes that may contain invalid start and end dates. This process may take a while to complete depending on the number of discounts you have. We\'ll let you know when the process is complete.', 'easy-digital-downloads' ), + ) + ); + } + } + + /** + * Gets the percentage complete. + * + * @since 3.2.10 + * + * @return int + */ + private function get_percentage_complete() { + static $percent_complete; + + if ( ! is_null( $percent_complete ) ) { + return $percent_complete; + } + + // Get the total amount of rows remaining. + $total_rows_remaining = $this->get_discounts( true ); + + // Get the total we started with. + $total_rows_start = get_option( $this->total_count_option ); + + // Format a % complete without decimals. + $percent_complete = number_format( ( ( $total_rows_start - $total_rows_remaining ) / $total_rows_start ) * 100, 0 ); + + // Just in case we end up over 100%, somehow...make it 100%. + if ( $percent_complete > 100 ) { + $percent_complete = 100; + } + + return $percent_complete; + } + + /** + * Get discounts with empty start or end dates. + * + * @since 3.2.10 + * + * @param bool $count Whether to return the count of discounts. + * + * @return array|bool + */ + private function get_discounts( $count = false ) { + $get_adjustments_function = $count ? 'edd_count_adjustments' : 'edd_get_adjustments'; + + $discounts = $get_adjustments_function( + array( + 'type' => 'discount', + 'date_query' => array( + 'relation' => 'OR', + array( + 'column' => 'start_date', + 'value' => $this->zeroed_value, + 'compare' => '=', + ), + array( + 'column' => 'end_date', + 'value' => $this->zeroed_value, + 'compare' => '=', + ), + ), + ) + ); + + if ( ! empty( $discounts ) ) { + return $discounts; + } + + return false; + } +} diff --git a/src/Upgrades/Emails/Registration.php b/src/Upgrades/Emails/Registration.php new file mode 100644 index 00000000000..45aa3d60b6b --- /dev/null +++ b/src/Upgrades/Emails/Registration.php @@ -0,0 +1,56 @@ + array( 'install' ), + ); + } + + /** + * Install the email templates. + * + * @since 3.3.0 + * @return void + */ + public function install() { + $registry = new Registry(); + foreach ( $registry->get_emails() as $email_id => $email_class ) { + $email = $registry->make_email_class( $email_class, array( $email_id ) ); + $email->install(); + } + edd_set_upgrade_complete( self::$upgrade_name ); + } +} diff --git a/src/Upgrades/Loader.php b/src/Upgrades/Loader.php new file mode 100644 index 00000000000..ee0580473af --- /dev/null +++ b/src/Upgrades/Loader.php @@ -0,0 +1,49 @@ + 'add_events', + ); + } + + /** + * Add the upgrade events. + * + * @since 3.2.10 + * @return void + */ + public function add_events() { + $upgrade_classes = array( + new Orders\MigrateAfterActionsDate(), + new Adjustments\DiscountsStartEnd(), + new Emails\Registration(), + ); + + $events = new EventManager(); + foreach ( $upgrade_classes as $upgrade_class ) { + $events->add_subscriber( $upgrade_class ); + } + } +} diff --git a/src/Upgrades/Orders/MigrateAfterActionsDate.php b/src/Upgrades/Orders/MigrateAfterActionsDate.php new file mode 100644 index 00000000000..c034f91903f --- /dev/null +++ b/src/Upgrades/Orders/MigrateAfterActionsDate.php @@ -0,0 +1,381 @@ + 'process_step', + ); + + if ( ! self::next_scheduled( 'edd_migrate_order_actions_date' ) ) { + $hooks['shutdown'] = array( 'maybe_schedule_background_update', 99 ); + } + + return $hooks; + } + + /** + * Maybe schedules the background update. + * + * We're running this on shutdown, so we can be sure that the Orders table has been created. + * + * @since 3.2.0 + * + * @return void + */ + public function maybe_schedule_background_update() { + $orders_table = new \EDD\Database\Tables\Orders(); + $table_version_key = $orders_table->db_version_key; + $table_version = get_option( $table_version_key, 0 ); + + // If our table version is earlier than the one we need, return. + if ( version_compare( '202307111', $table_version, '<' ) ) { + return; + } + + // This is the right table, and the right version, so let's schedule the background update. + $this->register_first_background_event(); + } + + /** + * Registers the first background event to start the migration. + * + * @since 3.2.0 + * + * @return void + */ + private function register_first_background_event() { + // If we've already scheduled the cleanup, no need to schedule it again. + if ( self::next_scheduled( $this->cron_action ) ) { + return; + } + + // See if we have any order meta with the key of _edd_complete_actions_run. + global $wpdb; + $has_action_date_meta = $wpdb->get_var( "SELECT COUNT(meta_id) FROM {$this->get_meta_table_name()} WHERE meta_key = '_edd_complete_actions_run'" ); + + if ( empty( $has_action_date_meta ) ) { + $this->mark_complete( false ); + return; + } + + // Only update the total count option if it doesn't exist. This prevents it from being overridden in some edge cases. + if ( empty( get_option( $this->total_count_option ) ) ) { + // Set the total amount in a transient so we can use it later. + update_option( $this->total_count_option, $has_action_date_meta, false ); + } + + $this->add_or_update_initial_notification(); + + // ...And schedule a single event a minute from now to start the processing of this data. + SingleEvent::add( + time() + MINUTE_IN_SECONDS, + $this->cron_action + ); + } + + /** + * Processes a single step of the migration. + * + * If there are more items to process, it will schedule another single event to run in a minute. + * If there are no more items to process, it will mark the migration as complete. + * + * @since 3.2.0 + * + * @return void + */ + public function process_step() { + // Since this hooks on an action, don't let it run if we're not in a cron. + if ( ! edd_doing_cron() ) { + return; + } + + global $wpdb; + $meta_rows = $wpdb->get_results( "SELECT meta_id, edd_order_id, meta_value FROM {$this->get_meta_table_name()} WHERE meta_key = '_edd_complete_actions_run' ORDER BY edd_order_id DESC LIMIT 500" ); + + edd_debug_log( 'Found records to migrate (max 500): ' . count( $meta_rows ) ); + + // If we don't have anymore items to process, mark it as complete. + if ( empty( $meta_rows ) ) { + $this->mark_complete(); + return; + } + + // Iterate through the meta rows and update the order. + $migrated_meta_ids = array(); + $migrated_note_ids = array(); + + // We don't need to schedule a recalculation here. + add_filter( 'edd_recalculate_bypass_cron', '__return_true' ); + + foreach ( $meta_rows as $row ) { + // Convert the timestamp to a DateTime object. + + $date = new Date(); + $date->setTimestamp( $row->meta_value )->setTimezone( new \DateTimeZone( 'UTC' ) ); + + // Update the order. + $updated = edd_update_order( $row->edd_order_id, array( 'date_actions_run' => $date->format( 'mysql' ) ) ); + + if ( $updated ) { + // Store the meta ids we can remove. + $migrated_meta_ids[] = $row->meta_id; + + // Store the note ids we can remove. + $note_content = __( 'After payment actions processed.', 'easy-digital-downloads' ); + $note_id = $wpdb->get_var( "SELECT id FROM {$this->get_notes_table_name()} WHERE object_id = {$row->edd_order_id} AND object_type = 'order' AND content = '{$note_content}'" ); + if ( $note_id ) { + $migrated_note_ids[] = $note_id; + } + } + } + + // Remove our filter for bypassing the cron. + remove_filter( 'edd_recalculate_bypass_cron', '__return_true' ); + + $migrated_meta_ids = array_filter( $migrated_meta_ids ); + // If we have any migrated meta IDs, delete them. + if ( ! empty( $migrated_meta_ids ) ) { + // Delete the meta rows we just migrated. + $wpdb->query( "DELETE FROM {$this->get_meta_table_name()} WHERE meta_id IN (" . implode( ',', $migrated_meta_ids ) . ')' ); + } + + $migrated_note_ids = array_filter( $migrated_note_ids ); + // If we found notes to delete, delete them. + if ( ! empty( $migrated_note_ids ) ) { + $wpdb->query( "DELETE FROM {$this->get_notes_table_name()} WHERE id IN (" . implode( ',', $migrated_note_ids ) . ')' ); + } + + $this->add_or_update_initial_notification(); + + $percent_complete = $this->get_percentage_complete(); + + edd_debug_log( 'Processed step of order actions date migration. Percentage Complete: ' . $percent_complete . '%' ); + + // ...And schedule another single event so we can process the next batch. + SingleEvent::add( + time() + MINUTE_IN_SECONDS, + $this->cron_action + ); + } + + /** + * Marks the migration as complete. + * + * @since 3.2.0 + * + * @param bool $add_notification If we should add a notification that the migration is complete. + * + * @return void + */ + private function mark_complete( $add_notification = true ) { + // Set the upgrade as complete. + edd_set_upgrade_complete( 'migrate_order_actions_date' ); + + // Delete the total count option. It may not exist, but we should delete it anyway. + delete_option( $this->total_count_option ); + + if ( false === $add_notification ) { + return; + } + + $initial_notification = $this->get_initial_notification(); + if ( ! empty( $initial_notification ) ) { + EDD()->notifications->update( $initial_notification->id, array( 'dismissed' => 1 ) ); + } + + EDD()->notifications->maybe_add_local_notification( + array( + 'remote_id' => $this->complete_remote_id, + 'buttons' => '', + 'conditions' => '', + 'type' => 'success', + 'title' => __( 'Order Table Optimization Complete!', 'easy-digital-downloads' ), + 'content' => __( 'Easy Digital Downloads has finished updating your orders database! Thank you for your patience.', 'easy-digital-downloads' ), + ) + ); + } + + /** + * Gets the initial notification about the migration. + * + * @since 3.2.0 + * + * @return object + */ + private function get_initial_notification() { + return EDD()->notifications->get_item_by( 'remote_id', $this->in_progress_remote_id ); + } + + /** + * Adds or updates the initial notification about the migration. + * + * @since 3.2.0 + * + * @return void + */ + private function add_or_update_initial_notification() { + $initial_notification = $this->get_initial_notification(); + $percent_complete = $this->get_percentage_complete(); + + /* translators: %s: % complete. */ + $notification_title = sprintf( __( 'Optimizing Orders Table ( %d%% )', 'easy-digital-downloads' ), $percent_complete ); + + if ( ! empty( $initial_notification ) ) { + $date = new Date(); + $date->setTimestamp( time() )->setTimezone( new \DateTimeZone( 'UTC' ) ); + + // Update the notification. + EDD()->notifications->update( + $initial_notification->id, + array( + 'title' => $notification_title, + 'date_created' => $date->format( 'mysql' ), + ) + ); + } else { + // Add the notification. + EDD()->notifications->maybe_add_local_notification( + array( + 'remote_id' => $this->in_progress_remote_id, + 'buttons' => '', + 'conditions' => '', + 'type' => 'info', + 'title' => $notification_title, + 'content' => __( 'Easy Digital Downloads is updating the Orders and Order Meta table in the background. This process may take a while to complete depending on the number of orders you have. We\'ll let you know when the process is complete.', 'easy-digital-downloads' ), + ) + ); + } + } + + /** + * Gets the name of the order meta table. + * + * @since 3.2.0 + * + * @return string + */ + private function get_meta_table_name() { + $order_meta_table = new \EDD\Database\Tables\Order_Meta(); + return $order_meta_table->table_name; + } + + /** + * Gets the name of the notes table. + * + * @since 3.2.0 + * + * @return string + */ + private function get_notes_table_name() { + $notes_table = new \EDD\Database\Tables\Notes(); + return $notes_table->table_name; + } + + /** + * Gets the percentage complete. + * + * @since 3.2.0 + * + * @return int + */ + private function get_percentage_complete() { + static $percent_complete; + + if ( ! is_null( $percent_complete ) ) { + return $percent_complete; + } + + global $wpdb; + + // Get the total amount of rows remaining. + $total_rows_remaining = $wpdb->get_var( "SELECT COUNT(*) FROM {$this->get_meta_table_name()} WHERE meta_key = '_edd_complete_actions_run'" ); + + // Get the total we started with. + $total_rows_start = get_option( $this->total_count_option ); + + // Format a % complete without decimals. + $percent_complete = number_format( ( ( $total_rows_start - $total_rows_remaining ) / $total_rows_start ) * 100, 0 ); + + // Just in case we end up over 100%, somehow...make it 100%; + if ( $percent_complete > 100 ) { + $percent_complete = 100; + } + + return $percent_complete; + } +} diff --git a/src/Upgrades/Utilities/MigrationCheck.php b/src/Upgrades/Utilities/MigrationCheck.php new file mode 100644 index 00000000000..bcf5b869b7c --- /dev/null +++ b/src/Upgrades/Utilities/MigrationCheck.php @@ -0,0 +1,40 @@ +set_gmt_offset(); + $this->set_date_format(); + $this->set_time_format(); + $this->set_time_zone(); + } + + /** + * 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. + $known_formats = array( 'date', 'time', 'datetime', 'mysql', 'date-attribute', 'date-js', 'date-mysql', 'time-mysql' ); + if ( ! in_array( $format, $known_formats, 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 ) { + + $localize = (bool) $localize; + + // Fallback to this time zone. + if ( null === $timezone ) { + $timezone = $localize ? $this->get_time_zone() : 'UTC'; + } + + // If the date string cannot be property converted to a valid time, reset it to now. + if ( ! $date_string || ! 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() { + $gmt_offset = get_option( 'gmt_offset', 0 ); + if ( empty( $gmt_offset ) ) { + $gmt_offset = 0; + } + $this->gmt_offset = $gmt_offset * 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. + $time_zone = 'UTC'; + + // Get some useful values. + $timezone = get_option( 'timezone_string' ); + $gmt_offset = get_option( 'gmt_offset' ); + + // Use timezone string if it's available. + if ( ! empty( $timezone ) ) { + $time_zone = $timezone; + } elseif ( ! empty( $gmt_offset ) && is_numeric( $gmt_offset ) ) { + // Use GMT offset to calculate. + $gmt_offset = $gmt_offset * HOUR_IN_SECONDS; + $hours = abs( intval( $gmt_offset / HOUR_IN_SECONDS ) ); + $hours = str_pad( $hours, 2, '0', STR_PAD_LEFT ); + $minutes = abs( floor( ( $gmt_offset / MINUTE_IN_SECONDS ) % MINUTE_IN_SECONDS ) ); + $minutes = str_pad( $minutes, 2, '0', STR_PAD_LEFT ); + $math = ( $gmt_offset >= 0 ) ? '+' : '-'; + $time_zone = "GMT{$math}{$hours}:{$minutes}"; + } + + // Set. + $this->time_zone = $time_zone; + } +} diff --git a/src/Utils/Convert.php b/src/Utils/Convert.php new file mode 100644 index 00000000000..a3079e390c7 --- /dev/null +++ b/src/Utils/Convert.php @@ -0,0 +1,36 @@ + self::get_expiration( $expiration ), + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ), + $expiration + ); + } + + /** + * Retrieves the expiration time for a cookie. + * + * @since 3.3.0 + * @param int|string $expiration The expiration time for the cookie. + * @return int The calculated expiration time for the cookie. + */ + private static function get_expiration( $expiration ) { + if ( is_null( $expiration ) ) { + $expiration = time() - 3600; + } + + return $expiration; + } +} diff --git a/src/Utils/Countries.php b/src/Utils/Countries.php new file mode 100644 index 00000000000..7484662701a --- /dev/null +++ b/src/Utils/Countries.php @@ -0,0 +1,93 @@ +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 ); + if ( array_key_exists( $state_code, $states ) ) { + return $states[ $state_code ]; + } + + return 'GB' === $country_code ? $this->maybe_get_legacy_gb_states( $state_code, $country_code ) : $state_code; + } + + /** + * Retrieves the legacy GB states based on the state code and country code. + * + * @since 3.3.0 + * @param string $state_code The state code. + * @return mixed The legacy GB states if found, otherwise null. + */ + private function maybe_get_legacy_gb_states( $state_code ) { + $states = $this->get_legacy_gb_states(); + if ( array_key_exists( $state_code, $states ) ) { + return $states[ $state_code ]; + } + + return $state_code; + } + + /** + * Retrieves the legacy states for Great Britain. + * + * @since 3.3.0 + * @return array The array of legacy states for Great Britain. + */ + private function get_legacy_gb_states() { + return include EDD_PLUGIN_DIR . 'i18n/states-gb-legacy.php'; + } +} diff --git a/src/Utils/Data/Serializer.php b/src/Utils/Data/Serializer.php new file mode 100644 index 00000000000..5e7a4c85ce7 --- /dev/null +++ b/src/Utils/Data/Serializer.php @@ -0,0 +1,84 @@ + false ) ); + } + + return $data; + } + + /** + * Fixes corrupted serialized data. + * + * @since 3.2.10 + * @param string $data Serialized data. + * @return string + */ + public static function fix_corrupted_serialized_data( $data ) { + return preg_replace_callback( + '!s:\d+:"(.*?)";!s', + function ( $m ) { + return 's:' . mb_strlen( $m[1] ) . ':"' . $m[1] . '";'; + }, + $data + ); + } +} diff --git a/src/Utils/Date.php b/src/Utils/Date.php new file mode 100644 index 00000000000..9b5cf652a58 --- /dev/null +++ b/src/Utils/Date.php @@ -0,0 +1,121 @@ +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/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/Error_Logger_Interface.php b/src/Utils/Error_Logger_Interface.php new file mode 100644 index 00000000000..29811288cbb --- /dev/null +++ b/src/Utils/Error_Logger_Interface.php @@ -0,0 +1,36 @@ +move( $uploads_file, $file ); + } + } + + /** + * Check if a file exists. + * + * @since 3.3.4 + * @param string $file The file to check. + * @return bool True if the file exists, false otherwise. + */ + public static function file_exists( $file ) { + // Strip any protocol/file wrappers. + $file = self::sanitize_file_path( $file ); + if ( ! self::is_direct() ) { + return file_exists( $file ); + } + + return self::get_fs()->exists( $file ); + } + + /** + * Open a file. + * + * @since 3.3.4 + * @param string $file The file to open. + * @param string $mode The mode to open the file in. + * @return resource|bool The file resource on success, false on failure. + */ + public static function fopen( $file, $mode ) { + $file = self::sanitize_file_path( $file ); + + return @fopen( $file, $mode ); + } + + /** + * Get the filesize. + * + * @since 3.3.4 + * @param string $file The file to get the size of. + * @return int|bool The file size on success, false on failure. + */ + public static function size( $file ) { + $file = self::sanitize_file_path( $file ); + + return self::get_fs()->size( $file ); + } + + /** + * Get the file contents as a string. + * + * Returns the file contents as a string. If you need the file contents as an array (such as for CSV files), use `file()` instead. + * + * @since 3.3.4 + * @param string $file The file to get the contents of. + * @return string|bool The file contents on success, false on failure. + */ + public static function get_contents( $file ) { + $file = self::sanitize_file_path( $file ); + if ( ! self::is_direct() ) { + return file_get_contents( $file ); + } + + return self::get_fs()->get_contents( $file ); + } + + /** + * Write contents to a file. + * + * @since 3.3.4 + * @param string $file The file to write to. + * @param string $contents The contents to write. + * @return int|bool The number of bytes written to the file on success, false on failure. + */ + public static function put_contents( $file, $contents ) { + $file = self::sanitize_file_path( $file ); + if ( ! self::is_direct() ) { + return file_put_contents( $file, $contents ); + } + + return self::get_fs()->put_contents( $file, $contents ); + } + + /** + * Get the file contents as an array. + * + * Returns the file contents as an array. Each line in the file is an element in the array. + * + * @since 3.3.4 + * @param string $file The file to get the contents of. + * @return array|bool The file contents as an array on success, false on failure. + */ + public static function file( $file ) { + $file = self::sanitize_file_path( $file ); + if ( ! self::is_direct() ) { + return file( $file ); + } + + return self::get_fs()->get_contents_array( $file ); + } + + /** + * Get the modified time of a file. + * + * @since 3.3.4 + * @param string $file The file to get the modified time of. + * @return int|bool The modified time on success, false on failure. + */ + public static function filemtime( $file ) { + $file = self::sanitize_file_path( $file ); + + return self::get_fs()->mtime( $file ); + } + + /** + * Create a symbolic link to a file. + * + * @since 3.3.4 + * @param string $target The target of the link. + * @param string $link The link to create. + * @return bool True on success, false on failure. + */ + public static function symlink( $target, $link ) { + $target = self::sanitize_file_path( realpath( $target ) ); + $link = self::sanitize_file_path( $link ); + + return @symlink( $target, $link ); + } + + /** + * Sanitize a file path. + * + * Removes potentially risky protocols from the file path. + * + * @since 3.3.4 + * @param string $file The file path to sanitize. + * @return string The sanitized file path. + */ + public static function sanitize_file_path( $file ) { + // If the file path doesn't have a protocol just return it. + if ( false === strpos( $file, '://' ) && false === strpos( $file, urlencode( '://' ) ) ) { + return $file; + } + + $restricted_protocols = self::get_restricted_file_protocols(); + + foreach ( $restricted_protocols as $protocol ) { + // Create a case-insensitive pattern for each protocol. + $pattern = '#^' . preg_quote( $protocol, '#' ) . '#i'; + $file = preg_replace( $pattern, '', $file ); + } + + return $file; + } + + /** + * Get the restricted file protocols. + * + * @since 3.3.4 + * @return array The restricted file protocols. + */ + private static function get_restricted_file_protocols() { + /** + * Filter the protocols that are restricted from file paths. + * + * @since 3.3.4 + * + * @param array $protocols The protocols to restrict. + */ + $protocols = (array) apply_filters( + 'edd_file_system_restricted_protocols', + array( + 'phar://', + 'php://', + 'glob://', + 'data://', + 'expect://', + 'zip://', + 'rar://', + 'zlib://', + ), + ); + + // Now we need the URL encoded protocols to ensure we catch all variations. + return array_merge( + $protocols, + array_map( 'urlencode', $protocols ) + ); + } + + /** + * Check if the file system is direct. + * + * @since 3.3.4 + * @return bool True if the file system is direct, false otherwise. + */ + private static function is_direct() { + return self::get_fs() instanceof \WP_Filesystem_Direct; + } +} 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..5a4d83f0d90 --- /dev/null +++ b/src/Utils/NotificationImporter.php @@ -0,0 +1,255 @@ +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() { + $request = new \EDD\Utils\RemoteRequest( $this->getApiEndpoint() ); + + if ( is_wp_error( $request->response ) ) { + throw new \Exception( $request->response->get_error_message() ); + } + + return ! empty( $request->body ) ? json_decode( $request->body ) : 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/ProgressBar.php b/src/Utils/ProgressBar.php new file mode 100644 index 00000000000..9bfdc66b3c3 --- /dev/null +++ b/src/Utils/ProgressBar.php @@ -0,0 +1,136 @@ +args = wp_parse_args( + $args, + array( + 'current_percentage' => false, + 'size' => 'medium', + 'show_percentage' => false, + 'show_current' => false, + 'show_total' => false, + 'current_count' => 0, + 'total_count' => 100, + ) + ); + } + + /** + * Gets the progress bar markup. + * + * @since 3.2.0 + * @return string + */ + public function get() { + return sprintf( + '
    %s
    ', + $this->get_size(), + $this->get_current_percentage(), + $this->get_label(), + ); + } + + /** + * Gets the progress bar classes. + * + * We only support the sizes defined in CSS, so we'll only return those. + * + * @since 3.2.0 + * @return string + */ + private function get_size() { + if ( in_array( $this->args['size'], array( 'small', 'medium', 'large' ), true ) ) { + return $this->args['size']; + } + + return 'medium'; + } + + /** + * Gets the progress bar % complete. + * + * This is a formatted string, complete with the % sign. + * + * @since 3.2.0 + * @return string + */ + private function get_current_percentage() { + if ( is_numeric( $this->args['current_percentage'] ) ) { + $current_percentage = intval( $this->args['current_percentage'] ); + } elseif ( is_numeric( $this->args['current_count'] ) && is_numeric( $this->args['total_count'] ) ) { + $current_percentage = intval( $this->args['current_count'] / $this->args['total_count'] * 100 ); + } else { + $current_percentage = 0; + } + + // Protect from ever going over 100%. + if ( $current_percentage > 100 ) { + $current_percentage = 100; + } + + return $current_percentage . '%'; + } + + /** + * Gets the progress bar label. + * + * Determines what to show in the label, based on the passed in values for showing percentage, current count, and total count. + * + * @since 3.2.0 + * @return string + */ + private function get_label() { + $label = ''; + if ( $this->args['show_current'] ) { + $label .= sprintf( '%s', $this->args['current_count'] ); + } + + if ( $this->args['show_total'] ) { + if ( $this->args['show_current'] ) { + $label .= sprintf( ' / %d', $this->args['total_count'] ); + } else { + $label .= sprintf( '%s', $this->args['total_count'] ); + } + } + + if ( $this->args['show_percentage'] ) { + if ( $this->args['show_current'] || $this->args['show_total'] ) { + $label .= sprintf( ' (%s)', $this->get_current_percentage() ); + } else { + $label .= sprintf( '%s', $this->get_current_percentage() ); + } + } + + // If our label isn't empty, wrap it up for styling. + if ( ! empty( $label ) ) { + $label = '
    ' . $label . '
    '; + } + + return $label; + } +} diff --git a/src/Utils/RESTChecker.php b/src/Utils/RESTChecker.php new file mode 100644 index 00000000000..04963ea4045 --- /dev/null +++ b/src/Utils/RESTChecker.php @@ -0,0 +1,101 @@ +endpoint = $endpoint; + } + + /** + * Test if the REST API is accessible. + * + * The REST API might be inaccessible due to various security measures, + * or it might be completely disabled by a plugin. + * + * @since 3.2.0 + * @return bool + */ + public function is_enabled() { + + $response = $this->make_request(); + + // When testing the REST API, an error was encountered, leave early. + if ( is_wp_error( $response ) ) { + return false; + } + + // When testing the REST API, an unexpected result was returned, leave early. + if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return false; + } + + // If the remote response is valid JSON, the REST API is enabled. + return (bool) $this->is_json( wp_remote_retrieve_body( $response ) ); + } + + /** + * Make a request to the REST API. + * + * @since 3.2.0 + * @return array|WP_Error + */ + private function make_request() { + + return wp_safe_remote_get( + rest_url( $this->endpoint ), + array( + 'timeout' => 15, + 'cookies' => array(), + 'sslverify' => $this->sslverify(), + 'headers' => array( + 'Cache-Control' => 'no-cache', + ), + ) + ); + } + + /** + * Whether to verify SSL when making a request to the REST API. + * This filter is documented in wp-includes/class-wp-http-streams.php + * + * @since 3.2.0 + * @return bool + */ + private function sslverify() { + return apply_filters( 'https_local_ssl_verify', false ); + } + + /** + * Whether a string is valid JSON. + * + * @since 3.2.0 + * @param string $string + * @return bool + */ + private function is_json( $string ) { + return ( + is_string( $string ) && + is_array( json_decode( $string, true ) ) && + json_last_error() === JSON_ERROR_NONE + ); + } +} diff --git a/src/Utils/Registry.php b/src/Utils/Registry.php new file mode 100644 index 00000000000..f7d3d3eb433 --- /dev/null +++ b/src/Utils/Registry.php @@ -0,0 +1,152 @@ +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/src/Utils/RemoteRequest.php b/src/Utils/RemoteRequest.php new file mode 100644 index 00000000000..5f2d743d5d7 --- /dev/null +++ b/src/Utils/RemoteRequest.php @@ -0,0 +1,188 @@ +url = $url; + $this->args = $this->parse_args( $args ); + $this->send_request(); + } + + /** + * Magic getter. + * + * @since 3.3.5 + * @param string $key The property name. + * @return mixed + */ + public function __get( $key ) { + if ( ! property_exists( $this, $key ) ) { + return null; + } + + if ( is_callable( array( $this, "get_{$key}" ) ) ) { + return $this->{"get_{$key}"}(); + } + + return $this->$key; + } + + /** + * Parses the request arguments. + * + * @since 3.3.5 + * @param array $args The request arguments. + * @return array + */ + private function parse_args( array $args ) { + return wp_parse_args( + $args, + array( + 'timeout' => 15, + 'sslverify' => true, + 'user-agent' => $this->get_user_agent(), + 'reject_unsafe_urls' => true, + ) + ); + } + + /** + * Sends the request. + * + * @since 3.3.5 + */ + private function send_request() { + $this->response = wp_remote_request( + esc_url_raw( $this->url ), + $this->args + ); + } + + /** + * Gets the response. + * + * @since 3.3.5 + * @return false|array|WP_Error + */ + private function get_response() { + return $this->response; + } + + /** + * Parses the request response. + * + * @since 3.3.5 + * @return false|\WP_Error|string + */ + private function get_body() { + if ( is_null( $this->body ) ) { + $this->body = wp_remote_retrieve_body( $this->get_response() ); + } + + return $this->body; + } + + /** + * Gets the response code. + * + * @since 3.3.5 + * @return int + */ + private function get_code() { + if ( is_null( $this->code ) ) { + $response = $this->get_response(); + if ( ! $response || is_wp_error( $response ) ) { + $this->code = 404; + } else { + $this->code = wp_remote_retrieve_response_code( $response ); + } + } + + return $this->code; + } + + /** + * Gets the default user agent string. + * + * @since 3.3.5 + * @return string + */ + private function get_user_agent() { + $edd = edd_is_pro() ? 'EDDPro/' : 'EDD/'; + $user_agent = array( + 'WordPress/' . get_bloginfo( 'version' ), + $edd . EDD_VERSION, + get_bloginfo( 'url' ), + ); + + return implode( '; ', $user_agent ); + } +} diff --git a/src/Utils/Request.php b/src/Utils/Request.php new file mode 100644 index 00000000000..73c4b6387a1 --- /dev/null +++ b/src/Utils/Request.php @@ -0,0 +1,150 @@ +is_block_editor ); + } +} diff --git a/src/Utils/Static_Registry.php b/src/Utils/Static_Registry.php new file mode 100644 index 00000000000..6e7ebc49552 --- /dev/null +++ b/src/Utils/Static_Registry.php @@ -0,0 +1,30 @@ +args = wp_parse_args( + $args, + array( + 'status' => 'default', + 'label' => '', + 'icon' => '', + 'color' => '', + 'dashicon' => true, + 'class' => '', + 'position' => 'after', + ) + ); + } + + /** + * 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 ''; + } + if ( empty( $icon ) ) { + $icon = $this->get_icon(); + } + + return sprintf( + '%4$s%2$s%3$s', + $this->get_class_string( $this->get_classes() ), + esc_html( $this->args['label'] ), + 'after' === $this->args['position'] ? $icon : '', + 'before' === $this->args['position'] ? $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() { + $classes = array( + 'edd-status-badge', + "edd-status-badge--{$this->args['status']}", + $this->get_color_class(), + ); + if ( is_array( $this->args['class'] ) ) { + $classes = array_merge( $classes, $this->args['class'] ); + } elseif ( ! empty( $this->args['class'] ) ) { + $classes[] = $this->args['class']; + } + + return $classes; + } + + /** + * 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/src/Utils/Transient.php b/src/Utils/Transient.php new file mode 100644 index 00000000000..f192f2461b7 --- /dev/null +++ b/src/Utils/Transient.php @@ -0,0 +1,104 @@ +option_name = $option_name; + $this->timeout = strtotime( $timeout, time() ); + } + + /** + * Gets the option value. + * + * @since 3.3.5 + * @return mixed + */ + public function get() { + $option = get_option( $this->option_name, false ); + if ( ! $option ) { + return false; + } + if ( is_string( $option ) ) { + $option = json_decode( $option, true ); + } + + return ! $this->is_expired( $option ) ? $option['value'] : false; + } + + /** + * Sets the option value. + * + * @since 3.3.5 + * @param mixed $data The data to set. + * @return bool + */ + public function set( $data ) { + $option = wp_json_encode( + array( + 'value' => $data, + 'timeout' => $this->timeout, + ) + ); + + return update_option( $this->option_name, $option, false ); + } + + /** + * Deletes the option. + * + * @since 3.3.5 + * @return bool + */ + public function delete() { + return delete_option( $this->option_name ); + } + + /** + * Checks whether a given option has "expired". + * + * @since 3.3.5 + * @param array|false $option The option value. + * @return bool + */ + private function is_expired( $option ) { + return empty( $option['timeout'] ) || time() > $option['timeout']; + } +} 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..eb965ae319e --- /dev/null +++ b/templates/emails/header-default.php @@ -0,0 +1,109 @@ + + + + + + <?php echo get_bloginfo( 'name' ); ?> + + +
    + + +
    + +
    + ' . get_bloginfo( 'name' ) . '

    '; ?> +
    + + + {begin_heading} + + + + {end_heading} + +
    + + + + + +
    +

    {heading}

    +
    + +
    + + + +
    + + + +
    +
    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..524d4697714 --- /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..799263ef802 --- /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, $order ) ); ?>
    :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 ) ) : ?> +
    + + + is_deliverable() && 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/stripe-success.php b/templates/stripe-success.php new file mode 100644 index 00000000000..3778178814b --- /dev/null +++ b/templates/stripe-success.php @@ -0,0 +1,16 @@ + +
    +

    + +

    +
    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..f81dcc94e8c --- /dev/null +++ b/tests/README.md @@ -0,0 +1,37 @@ +# 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 8.2 -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). + +While writing tests, you may found it helpful to only run the specific tests, file or namespace you are working in. To do this you can use the `-f` filter. + +``` +# Test for a specific test +bin/run-tests-local.sh -p 8.2 -w latest -f test_full_function_symbol + +# Test for any tests whose function starts with the string +bin/run-tests-local.sh -p 8.2 -w latest -f test_get_order_ + +# Test for a namespace or class +bin/run-tests-local.sh -p 8.2 -w latest -f 'EDD\\Tests\\Settings' +bin/run-tests-local.sh -p 8.2 -w latest -f 'EDD\\Tests\Settings\Setting' 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/api/tests-keys-list-table.php b/tests/api/tests-keys-list-table.php new file mode 100644 index 00000000000..aa648c3f62d --- /dev/null +++ b/tests/api/tests-keys-list-table.php @@ -0,0 +1,191 @@ +user->create( array( + 'role' => 'administrator', + ) ); + + // Give the user the required caps. + $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(); + + // Generate an API Key for the user. + $_POST['edd_set_api_key'] = 1; + EDD()->api->generate_api_key( self::$user_id ); + + // Require the list table class. + require_once EDD_PLUGIN_DIR . 'includes/admin/class-api-keys-table.php'; + + self::$list_table = new \EDD_API_Keys_Table(); + self::$items = self::$list_table->query(); + } + + /** + * Verify the plural, singular, and ajax properties. + */ + public function test_properties() { + $this->assertSame( 'api-keys', self::$list_table->_args['plural'] ); + $this->assertSame( 'api-key', self::$list_table->_args['singular'] ); + $this->assertFalse( self::$list_table->_args['ajax'] ); + } + + /** + * @covers \EDD_API_Keys_Table::get_columns() + */ + public function test_get_columns() { + $columns = self::$list_table->get_columns(); + + $this->assertArrayHasKey( 'user', $columns ); + $this->assertArrayHasKey( 'key', $columns ); + $this->assertArrayHasKey( 'token', $columns ); + $this->assertArrayHasKey( 'secret', $columns ); + $this->assertArrayHasKey( 'last_used', $columns ); + } + + /** + * @covers \EDD_API_Keys_Table::query() + */ + public function test_query() { + + $results = self::$list_table->query(); + + $this->assertSame( 1, count( $results ) ); + } + + /** + * @covers \EDD_API_Keys_Table::total_items() + */ + public function test_total_items() { + $total_items = self::$list_table->total_items(); + + $this->assertSame( 1, $total_items ); + } + + /** + * Verify the pagination args. + * + * @covers \EDD_API_Keys_Table::get_pagination_arg() + */ + public function test_pagination_args() { + self::$list_table->prepare_items(); + $this->assertSame( 1, self::$list_table->get_pagination_arg( 'total_items' ) ); + $this->assertSame( 1, self::$list_table->get_pagination_arg( 'total_pages' ) ); + $this->assertSame( 30, self::$list_table->get_pagination_arg( 'per_page' ) ); + } + + /** + * @covers \EDD_API_Keys_Table::column_key() + */ + public function test_column_key() { + $key = self::$list_table->column_key( self::$items[ self::$user_id ] ); + + $this->assertSame( '', $key ); + } + + /** + * @covers \EDD_API_Keys_Table::column_token() + */ + public function test_column_token() { + $token = self::$list_table->column_token( self::$items[ self::$user_id ] ); + + $this->assertSame( '', $token ); + } + + /** + * @covers \EDD_API_Keys_Table::column_secret() + */ + public function test_column_secret() { + $secret = self::$list_table->column_secret( self::$items[ self::$user_id ] ); + + $this->assertSame( '', $secret ); + } + + /** + * @covers \EDD_API_Keys_Table::column_last_used() + */ + public function test_column_last_used_never() { + $last_used = self::$list_table->column_last_used( self::$items[ self::$user_id ] ); + + $this->assertSame( 'Never Used', $last_used ); + } + + /** + * @covers \EDD_API_Keys_Table::column_last_used() + */ + public function test_column_last_used_is_used() { + // Create a api access log entry. + edd_add_api_request_log( + array( + 'user_id' => self::$user_id, + 'api_key' => self::$items[ self::$user_id ]['key'], + 'token' => self::$items[ self::$user_id ]['token'], + 'version' => 'v2', + 'error' => '', + 'ip' => '127.0.0.1', + 'time' => 0.0001, + 'request' => http_build_query( + array( + 'edd-api' => 'test', + 'key' => self::$items[ self::$user_id ]['key'], + 'token' => self::$items[ self::$user_id ]['token'], + 'query' => null, + 'type' => null, + ) + ) + ) + ); + + $items = self::$list_table->query(); + $last_used = self::$list_table->column_last_used( $items[ self::$user_id ] ); + $this->assertStringContainsString( ' second', $last_used ); + $this->assertStringContainsString( ' ago', $last_used ); + } + + /** + * @covers \EDD_API_Keys_Table::column_user() + */ + public function test_column_user() { + $user = self::$list_table->column_user( self::$items[ self::$user_id ] ); + + $this->assertStringContainsString( '' . get_userdata( self::$user_id )->user_login . '', $user ); + } +} diff --git a/tests/api/tests-user-keys.php b/tests/api/tests-user-keys.php new file mode 100644 index 00000000000..3f751536673 --- /dev/null +++ b/tests/api/tests-user-keys.php @@ -0,0 +1,143 @@ +user->create( array( + 'role' => 'administrator', + ) ); + + // Give the user the required caps. + $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(); + + // Generate an API Key for the user. + $_POST['edd_set_api_key'] = 1; + EDD()->api->generate_api_key( self::$user_id ); + self::$api_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'edd_user_public_key' AND user_id = %d", self::$user_id ) ); + self::$api_secret = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'edd_user_secret_key' AND user_id = %d", self::$user_id ) ); + self::$api_token = hash( 'md5', self::$api_secret . self::$api_key ); + } + + public function test_get_user_with_key() { + $this->assertEquals( self::$user_id, EDD()->api->get_user( self::$api_key ) ); + } + + public function test_get_user_with_no_key_no_query_var() { + $this->assertFalse( EDD()->api->get_user() ); + } + + public function test_get_user_with_no_key_has_query_var() { + global $wp_query; + $wp_query->query_vars['key'] = self::$api_key; + + $this->assertEquals( self::$user_id, EDD()->api->get_user() ); + + unset( $wp_query->query_vars['key'] ); + } + + public function test_get_user_with_no_user() { + $this->assertFalse( EDD()->api->get_user( 'invalid-key' ) ); + } + + public function test_get_user_public_key() { + $this->assertEquals( self::$api_key, EDD()->api->get_user_public_key( self::$user_id ) ); + } + + public function test_get_user_public_key_no_user() { + $this->assertEquals( '', EDD()->api->get_user_public_key() ); + } + + public function test_get_user_secret_key() { + $this->assertEquals( self::$api_secret, EDD()->api->get_user_secret_key( self::$user_id ) ); + } + + public function test_get_user_secret_key_no_user() { + $this->assertEquals( '', EDD()->api->get_user_secret_key() ); + } + + public function test_get_token() { + $this->assertEquals( self::$api_token, EDD()->api->get_token( self::$user_id ) ); + } + + public function test_update_key() { + $_POST['edd_set_api_key'] = 1; + + EDD()->api->update_key( self::$user_id ); + + $user_public = EDD()->api->get_user_public_key( self::$user_id ); + $user_secret = EDD()->api->get_user_secret_key( self::$user_id ); + + $this->assertNotEmpty( $user_public ); + $this->assertNotEmpty( $user_secret ); + + // Since we've now changed the keys, we need to update the static vars. + self::$api_key = $user_public; + self::$api_secret = $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_revoke_key_no_user_id() { + $this->assertFalse( EDD()->api->revoke_api_key() ); + } + + public function test_revoke_key_invalid_user() { + $this->assertFalse( EDD()->api->revoke_api_key( 999999999 ) ); + } + + public function test_revoke_key_user_without_key() { + $user_id = self::factory()->user->create( array( + 'role' => 'administrator', + ) ); + + $this->assertFalse( EDD()->api->revoke_api_key( $user_id ) ); + } + + public function test_revoke_key() { + $this->assertTrue( EDD()->api->revoke_api_key( self::$user_id ) ); + } +} diff --git a/tests/blocks/tests-orders.php b/tests/blocks/tests-orders.php new file mode 100644 index 00000000000..1bd74077e59 --- /dev/null +++ b/tests/blocks/tests-orders.php @@ -0,0 +1,130 @@ +customer->create( array( 'user_id' => get_current_user_id() ) ); + self::$customer = new \EDD_Customer( self::$customer_id ); + } + + public function setUp(): void { + Helpers\EDD_Helper_Download::add_download_files(); + } + + public function tearDown(): void { + Helpers\EDD_Helper_Download::remove_download_files(); + } + + public function test_get_purchased_products_default_args_no_orders() { + $products = Orders_Block\get_purchased_products( + array( + 'search' => false, + 'variations' => true, + 'nofiles' => __( 'No downloadable files found.', 'easy-digital-downloads' ), + 'hide_empty' => true, + ) + ); + + $this->assertFalse( $products ); + } + + public function test_get_purchased_products_has_orders() { + $order = parent::edd()->order->create_and_get( + array( + 'customer_id' => self::$customer_id, + 'user_id' => get_current_user_id(), + 'email' => self::$customer->email, + 'status' => 'complete', + ) + ); + + $products = Orders_Block\get_purchased_products( + array( + 'search' => false, + 'variations' => true, + 'nofiles' => __( 'No downloadable files found.', 'easy-digital-downloads' ), + 'hide_empty' => true, + ) + ); + + $this->assertSame( 1, count( $products ) ); + + // Clean up. + parent::edd()->order->delete( $order->ID ); + } + + public function test_get_purchased_products_has_orders_all_not_deliverable() { + $order = parent::edd()->order->create_and_get( + array( + 'customer_id' => self::$customer_id, + 'user_id' => get_current_user_id(), + 'email' => self::$customer->email, + 'status' => 'refunded', + ) + ); + + + $products = Orders_Block\get_purchased_products( + array( + 'search' => false, + 'variations' => true, + 'nofiles' => __( 'No downloadable files found.', 'easy-digital-downloads' ), + 'hide_empty' => true, + ) + ); + + $this->assertFalse( $products ); + + // Clean up. + parent::edd()->order->delete( $order->ID ); + } + + public function test_get_purchased_products_has_orders_single_not_deliverable() { + $order = parent::edd()->order->create_and_get( + array( + 'customer_id' => self::$customer_id, + 'user_id' => get_current_user_id(), + 'email' => self::$customer->email, + 'status' => 'completed', + ) + ); + + edd_update_order_item( + $order->items[0]->id, + array( + 'status' => 'refunded', + ) + ); + + $products = Orders_Block\get_purchased_products( + array( + 'search' => false, + 'variations' => true, + 'nofiles' => __( 'No downloadable files found.', 'easy-digital-downloads' ), + 'hide_empty' => true, + ) + ); + + $this->assertFalse( $products ); + + // Clean up. + parent::edd()->order->delete( $order->ID ); + } +} diff --git a/tests/blocks/tests-registration.php b/tests/blocks/tests-registration.php new file mode 100644 index 00000000000..0a0222a5242 --- /dev/null +++ b/tests/blocks/tests-registration.php @@ -0,0 +1,136 @@ +assertTrue( edd_has_core_blocks() ); + } + + public function test_block_categories_includes_edd() { + $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' ); + + $this->assertTrue( in_array( 'easy-digital-downloads', $categories, true ) ); + } + + public function test_button_colors_update_button_class() { + $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' ); + } + + public function test_checkout_block_is_registered() { + $block = self::$registry->get_registered( 'edd/checkout' ); + + $this->assertEquals( 'edd/checkout', $block->name ); + } + + public function test_cart_block_is_registered() { + $block = self::$registry->get_registered( 'edd/cart' ); + + $this->assertEquals( 'edd/cart', $block->name ); + } + + public function test_downloads_block_is_registered() { + $block = self::$registry->get_registered( 'edd/downloads' ); + + $this->assertEquals( 'edd/downloads', $block->name ); + } + + public function test_buy_button_block_is_registered() { + $block = self::$registry->get_registered( 'edd/buy-button' ); + + $this->assertEquals( 'edd/buy-button', $block->name ); + } + + public function test_login_block_is_registered() { + $block = self::$registry->get_registered( 'edd/login' ); + + $this->assertEquals( 'edd/login', $block->name ); + } + + public function test_register_block_is_registered() { + $block = self::$registry->get_registered( 'edd/register' ); + + $this->assertEquals( 'edd/register', $block->name ); + } + + public function test_order_history_block_is_registered() { + $block = self::$registry->get_registered( 'edd/order-history' ); + + $this->assertEquals( 'edd/order-history', $block->name ); + } + + // test confirmation is registered + public function test_confirmation_block_is_registered() { + $block = self::$registry->get_registered( 'edd/confirmation' ); + + $this->assertEquals( 'edd/confirmation', $block->name ); + } + + public function test_receipt_block_is_registered() { + $block = self::$registry->get_registered( 'edd/receipt' ); + + $this->assertEquals( 'edd/receipt', $block->name ); + } + + public function test_user_downloads_block_is_registered() { + $block = self::$registry->get_registered( 'edd/user-downloads' ); + + $this->assertEquals( 'edd/user-downloads', $block->name ); + } + + public function test_terms_block_is_registered() { + $block = self::$registry->get_registered( 'edd/terms' ); + + $this->assertEquals( 'edd/terms', $block->name ); + } + + public function test_checkout_has_blocks_is_true() { + $this->assertTrue( \EDD\Blocks\Checkout\Functions\checkout_has_blocks() ); + } + + public function test_shortcode_checkout_has_blocks_is_false() { + $post_id = $this->factory->post->create( array( + 'post_title' => 'Test Page', + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => '[download_checkout]', + ) ); + + // The main checkout has blocks. + $this->assertTrue( \EDD\Blocks\Checkout\Functions\checkout_has_blocks() ); + $this->go_to( get_permalink( $post_id ) ); + do_action( 'template_redirect' ); // Necessary to trigger correct actions + // This secondary checkout does not. + $this->assertFalse( \EDD\Blocks\Checkout\Functions\checkout_has_blocks() ); + } + + public function test_edd_get_blocks_includes_core_blocks() { + $blocks = \EDD\Blocks\Styles\get_edd_blocks(); + + $this->assertTrue( in_array( 'edd/checkout', $blocks, true ) ); + $this->assertTrue( in_array( 'edd/cart', $blocks, true ) ); + $this->assertTrue( in_array( 'edd/login', $blocks, true ) ); + $this->assertTrue( in_array( 'edd/downloads', $blocks, true ) ); + $this->assertTrue( in_array( 'edd/receipt', $blocks, true ) ); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000000..52fb53a9488 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,86 @@ +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/checkout/tests-agreement.php b/tests/checkout/tests-agreement.php new file mode 100644 index 00000000000..8e52893e1f2 --- /dev/null +++ b/tests/checkout/tests-agreement.php @@ -0,0 +1,39 @@ +assertSame( 'I agree to the terms and conditions', edd_get_option( 'agree_text' ) ); + } + + public function test_agreement_option_sanitization() { + edd_update_option( 'agree_text', '' ); + + $this->assertSame( 'alert("I agree to the terms and conditions")', edd_get_option( 'agree_text' ) ); + } + + public function test_agreement_text_sanitization() { + $agreement_text = 'I agree to the terms and conditions'; + $sanitized_text = \EDD\Settings\Sanitize\Types\RichEditor::sanitize( $agreement_text ); + $this->assertSame( $agreement_text, $sanitized_text ); + + $agreement_text = ''; + $sanitized_text = \EDD\Settings\Sanitize\Types\RichEditor::sanitize( $agreement_text ); + $this->assertSame( 'alert("I agree to the terms and conditions")', $sanitized_text ); + } +} diff --git a/tests/checkout/tests-auto-register.php b/tests/checkout/tests-auto-register.php new file mode 100644 index 00000000000..077ae06c7bd --- /dev/null +++ b/tests/checkout/tests-auto-register.php @@ -0,0 +1,78 @@ +assertEquals( 'auto', edd_get_option( 'logged_in_only' ) ); + } + + public function test_get_option_show_register_form_both_returns_login() { + $this->assertEquals( 'login', edd_get_option( 'show_register_form', 'both' ) ); + } + + public function test_get_option_show_register_form_registration_returns_none() { + $this->assertEquals( 'none', edd_get_option( 'show_register_form', 'registration' ) ); + } + + public function test_get_option_show_register_form_login_returns_login() { + $this->assertEquals( 'login', edd_get_option( 'show_register_form', 'login' ) ); + } + + public function test_guest_payment_is_not_guest() { + $guest_payment_id = Helpers\EDD_Helper_Payment::create_simple_guest_payment(); + $this->assertFalse( edd_is_guest_payment( $guest_payment_id ) ); + } + + public function test_existing_user_is_error() { + $user_id = $this->factory->user->create(); + $user = get_user_by( 'id', $user_id ); + $auto_register = new \EDD\Checkout\AutoRegister(); + $auto_register->check_existing_user( + $user, + array( + 'guest_user_data' => array( + 'user_email' => $user->user_email, + ), + ), + array() + ); + + $this->assertArrayHasKey( 'email_used', edd_get_errors() ); + } + + public function test_create_user_from_purchase_data_is_user_id() { + $purchase_data = array( + 'user_info' => array( + 'email' => 'testautoregister@edd.local', + 'first_name' => 'Test', + 'last_name' => 'Auto Register', + ), + ); + $auto_register = new \EDD\Checkout\AutoRegister(); + $user_id = $auto_register->create_user( $purchase_data ); + + // check that getting the user returns a WP_User object + $user = get_user_by( 'id', $user_id ); + $this->assertInstanceOf( 'WP_User', $user ); + } +} diff --git a/tests/checkout/tests-checkout-errors.php b/tests/checkout/tests-checkout-errors.php new file mode 100644 index 00000000000..c88e91dccce --- /dev/null +++ b/tests/checkout/tests-checkout-errors.php @@ -0,0 +1,129 @@ +assertEmpty( edd_get_errors() ); + } + + public function test_edd_check_purchase_email_banned() { + edd_update_option( 'banned_emails', array( 'test@edd.local' ) ); + edd_check_purchase_email( array(), array( 'edd_email' => 'test@edd.local' ) ); + + $this->assertArrayHasKey( 'email_banned', edd_get_errors() ); + } + + public function test_edd_check_purchase_email_not_banned() { + edd_update_option( 'banned_emails', array( 'test@edd.local' ) ); + edd_check_purchase_email( array(), array( 'edd_email' => 'newemail@edd.local' ) ); + + $this->assertEmpty( edd_get_errors() ); + } + + public function test_edd_check_purchase_email_no_edd_email() { + edd_update_option( 'banned_emails', array( 'test@edd.local' ) ); + edd_check_purchase_email( array(), array() ); + + $this->assertEmpty( edd_get_errors() ); + } + + public function test_existing_user_is_error() { + wp_logout(); + $user_id = $this->factory->user->create(); + $user = get_user_by( 'id', $user_id ); + edd_add_customer( + array( + 'email' => $user->user_email, + 'user_id' => $user->ID, + 'name' => $user->display_name, + ) + ); + $errors = new \EDD\Checkout\Errors(); + $errors->check_existing_users( + $user, + array( + 'guest_user_data' => array( + 'user_email' => $user->user_email, + ), + ), + array() + ); + + $this->assertArrayHasKey( 'email_used', edd_get_errors() ); + } + + public function test_existing_email_different_customer_is_error() { + wp_logout(); + $user_id = $this->factory->user->create(); + $user = get_user_by( 'id', $user_id ); + $customer_id = edd_add_customer( + array( + 'email' => $user->user_email, + 'user_id' => $user->ID, + 'name' => $user->display_name, + ) + ); + $user_2_id = $this->factory->user->create(); + $user_2 = get_user_by( 'id', $user_2_id ); + edd_add_customer_email_address( + array( + 'email' => $user_2->user_email, + 'customer_id' => $customer_id, + ) + ); + // log this user in + wp_set_current_user( $user_2_id ); + $_POST['email'] = $user_2->user_email; + $errors = new \EDD\Checkout\Errors(); + $response = $errors->check_email_ajax(); + + $this->assertInstanceOf( 'WP_Error', $response ); + + edd_checkout_check_existing_email( array(), array() ); + + $this->assertArrayHasKey( 'edd-customer-email-exists', edd_get_errors() ); + } + + public function test_existing_email_deleted_customer_is_okay() { + wp_logout(); + $user_id = $this->factory->user->create(); + $user = get_user_by( 'id', $user_id ); + $customer_id = edd_add_customer( + array( + 'email' => $user->user_email, + 'user_id' => $user->ID, + 'name' => $user->display_name, + ) + ); + $user_2_id = $this->factory->user->create(); + $user_2 = get_user_by( 'id', $user_2_id ); + edd_add_customer_email_address( + array( + 'email' => $user_2->user_email, + 'customer_id' => $customer_id, + 'type' => 'secondary', + ) + ); + edd_delete_customer( $customer_id ); + // log this user in + wp_set_current_user( $user_2_id ); + $_POST['email'] = $user_2->user_email; + $errors = new \EDD\Checkout\Errors(); + + $this->assertTrue( $errors->check_email_ajax() ); + + edd_checkout_check_existing_email( array(), array() ); + + $this->assertEmpty( edd_get_errors() ); + } +} diff --git a/tests/checkout/tests-checkout-validator.php b/tests/checkout/tests-checkout-validator.php new file mode 100644 index 00000000000..c739374dfb8 --- /dev/null +++ b/tests/checkout/tests-checkout-validator.php @@ -0,0 +1,95 @@ +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_block() { + $post_id = $this->factory->post->create( array( + 'post_title' => 'Test Page', + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => '', + ) ); + $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 test_edd_is_checkout_ajax_is_true_shortcode() { + $post_id = $this->factory->post->create( array( + 'post_title' => 'Test Page', + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => '[download_checkout]', + ) ); + $_POST['current_page'] = $post_id; + add_filter( 'wp_doing_ajax', '__return_true' ); + + $this->assertTrue( edd_is_checkout() ); + + unset( $_POST['current_page'] ); + remove_filter( 'wp_doing_ajax', '__return_true' ); + } + + public function test_edd_is_checkout_ajax_is_true_block() { + $post_id = $this->factory->post->create( array( + 'post_title' => 'Test Page', + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => '', + ) ); + $_POST['current_page'] = $post_id; + add_filter( 'wp_doing_ajax', '__return_true' ); + + $this->assertTrue( edd_is_checkout() ); + + unset( $_POST['current_page'] ); + remove_filter( 'wp_doing_ajax', '__return_true' ); + } +} diff --git a/tests/checkout/tests-checkout.php b/tests/checkout/tests-checkout.php new file mode 100644 index 00000000000..e778fff6390 --- /dev/null +++ b/tests/checkout/tests-checkout.php @@ -0,0 +1,288 @@ +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_gateways' ) ); + + $this->assertIsString( edd_checkout_button_purchase() ); + $this->assertStringContainsString( '', edd_checkout_button_purchase() ); + + remove_filter( 'edd_enabled_payment_gateways', array( $this, 'modify_gateways' ) ); + } + + /** + * 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 modify_gateways( $gateways ) { + return array( 'test-gateway' ); + } +} diff --git a/tests/checkout/tests-purchase-form-user.php b/tests/checkout/tests-purchase-form-user.php new file mode 100644 index 00000000000..8e286dba3d5 --- /dev/null +++ b/tests/checkout/tests-purchase-form-user.php @@ -0,0 +1,192 @@ +factory->user->create(); + wp_set_current_user( $user_id ); + + $_POST = array( + 'edd_first' => 'John', + 'edd_last' => 'Doe', + 'edd_email' => 'john@doe.example', + ); + $valid_data = edd_purchase_form_validate_fields(); + $user = edd_get_purchase_form_user( $valid_data, false ); + $legacy = _edds_get_purchase_form_user( $valid_data ); + + // assert that $user and $legacy are the same array + $this->assertEquals( $user, $legacy ); + + $this->assertEquals( $user_id, $user['user_id'] ); + $this->assertEquals( $_POST['edd_email'], $valid_data['logged_in_user']['user_email'] ); + $this->assertEquals( $_POST['edd_email'], $user['user_email'] ); + $this->assertEmpty( $user['address'] ); + } + + public function test_edd_get_purchase_form_user_guest() { + + $_POST = array( + 'edd_first' => 'John', + 'edd_last' => 'Doe', + 'edd_email' => 'guest@edd.local', + ); + + $valid_data = edd_purchase_form_validate_fields(); + $user = edd_get_purchase_form_user( $valid_data, false ); + $legacy = _edds_get_purchase_form_user( $valid_data ); + + // assert that $user and $legacy are the same array + $this->assertEquals( $user, $legacy ); + + $this->assertEquals( $_POST['edd_email'], $user['user_email'] ); + $this->assertEquals( $_POST['edd_email'], $valid_data['guest_user_data']['user_email'] ); + } + + public function test_edd_get_purchase_form_user_guest_with_address() { + + $_POST = array( + 'edd_first' => 'John', + 'edd_last' => 'Doe', + 'edd_email' => 'guest@edd.local', + 'billing_country' => 'US', + 'card_address' => '123 Main St', + 'card_address_2' => 'Apt 1', + 'card_city' => 'Springfield', + 'card_state' => 'OR', + 'card_zip' => '97477', + ); + + $valid_data = edd_purchase_form_validate_fields(); + $user = edd_get_purchase_form_user( $valid_data, false ); + $legacy = _edds_get_purchase_form_user( $valid_data ); + + // assert that $user and $legacy are the same array + $this->assertEquals( $user, $legacy ); + + $this->assertEquals( $_POST['edd_email'], $user['user_email'] ); + $this->assertEquals( $_POST['edd_email'], $valid_data['guest_user_data']['user_email'] ); + $this->assertEquals( 'US', $user['address']['country'] ); + $this->assertEquals( '123 Main St', $user['address']['line1'] ); + $this->assertEquals( 'Apt 1', $user['address']['line2'] ); + $this->assertEquals( 'Springfield', $user['address']['city'] ); + $this->assertEquals( 'OR', $user['address']['state'] ); + $this->assertEquals( '97477', $user['address']['zip'] ); + } + + public function test_edd_get_purchase_form_register() { + $_POST = array( + 'edd_first' => 'John', + 'edd_last' => 'Doe', + 'edd_email' => 'newuser@edd.local', + 'edd_user_login' => 'newuser', + 'edd_user_pass' => 'password', + 'edd_user_pass_confirm' => 'password', + 'edd-purchase-var' => 'needs-to-register', + ); + + $valid_data = edd_purchase_form_validate_fields(); + $user = edd_get_purchase_form_user( $valid_data, false ); + + $this->assertEquals( $_POST['edd_email'], $user['user_email'] ); + $this->assertEquals( $_POST['edd_email'], $valid_data['new_user_data']['user_email'] ); + } + + public function test_edd_get_purchase_form_register_legacy_stripe() { + $_POST = array( + 'edd_first' => 'John', + 'edd_last' => 'Doe', + 'edd_email' => 'newstripe@edd.local', + 'edd_user_login' => 'newstripe', + 'edd_user_pass' => 'password', + 'edd_user_pass_confirm' => 'password', + 'edd-purchase-var' => 'needs-to-register', + ); + + $valid_data = edd_purchase_form_validate_fields(); + $user = _edds_get_purchase_form_user( $valid_data ); + + $this->assertEquals( $_POST['edd_email'], $user['user_email'] ); + $this->assertEquals( $_POST['edd_email'], $valid_data['new_user_data']['user_email'] ); + } + + public function test_edd_get_purchase_form_user_login() { + + $user_id = $this->factory->user->create(); + // update the user password to password + wp_set_password( 'password', $user_id ); + $user = get_user_by( 'id', $user_id ); + $_POST = array( + 'edd_first' => 'John', + 'edd_last' => 'Doe', + 'edd_email' => $user->user_email, + 'edd_user_login' => $user->user_login, + 'edd_user_pass' => 'password', + 'edd-purchase-var' => 'needs-to-login', + ); + + $valid_data = edd_purchase_form_validate_fields(); + $purchase_user = edd_get_purchase_form_user( $valid_data, false ); + $legacy = _edds_get_purchase_form_user( $valid_data ); + + // assert that $purchase_user and $legacy are the same array + $this->assertEquals( $purchase_user, $legacy ); + + $this->assertEquals( $user_id, $valid_data['login_user_data']['user_id'] ); + $this->assertEquals( $user->user_login, $valid_data['login_user_data']['user_login'] ); + // This assertion should be true, but it is not at this time. + // $this->assertEquals( $user_id, $purchase_user['user_id'] ); + } + + public function test_edd_get_purchase_form_user_ajax_is_true() { + add_filter( 'wp_doing_ajax', '__return_true' ); + + $_POST = array( + 'edd_first' => 'John', + 'edd_last' => 'Doe', + 'edd_email' => 'guest@edd.local', + ); + $valid_data = edd_purchase_form_validate_fields(); + + $this->assertTrue( edd_get_purchase_form_user( $valid_data ) ); + + remove_filter( 'wp_doing_ajax', '__return_true' ); + } + + public function test_edd_get_purchase_form_user_ajax() { + add_filter( 'wp_doing_ajax', '__return_true' ); + + $_POST = array( + 'edd_first' => 'John', + 'edd_last' => 'Doe', + 'edd_email' => 'guest@edd.local', + ); + + $valid_data = edd_purchase_form_validate_fields(); + $user = edd_get_purchase_form_user( $valid_data, false ); + $legacy = _edds_get_purchase_form_user( $valid_data ); + + $this->assertEquals( $user, $legacy ); + + remove_filter( 'wp_doing_ajax', '__return_true' ); + } +} diff --git a/tests/compatibility/tests-wordfence.php b/tests/compatibility/tests-wordfence.php new file mode 100644 index 00000000000..f4e0d965a11 --- /dev/null +++ b/tests/compatibility/tests-wordfence.php @@ -0,0 +1,57 @@ +assertTrue( class_exists( 'EDD\Compatibility\Plugins\Wordfence' ) ); + } + + /** + * Test if the Wordfence compatibility class is a subclass of the base compatibility class. + * + * @since 3.2.8 + * + * @return void + */ + public function test_wordfence_compatibility_class_is_subclass() { + $this->assertTrue( is_subclass_of( 'EDD\Compatibility\Plugins\Wordfence', 'EDD\Compatibility\Plugins\Plugin' ) ); + } + + /** + * Test that without a Wordfence class, the compatibility class is not loaded. + * + * @since 3.2.8 + */ + public function test_wordfence_compatibility_class_not_loaded() { + Loader::load_plugin_compatibility(); + + $this->assertTrue( array_key_exists( 'wordfence', Loader::get_loaded()['plugins'] ) ); + $this->assertFalse( Loader::get_loaded()['plugins']['wordfence'] ); + } + + public function test_wordfence_compatibility_class_is_loaded() { + // Include the stub for the Wordfence class. + require_once EDD_PLUGIN_DIR . 'tests/helpers/stubs/wordfence.php'; + + Loader::load_plugin_compatibility(); + + $this->assertTrue( array_key_exists( 'wordfence', Loader::get_loaded()['plugins'] ) ); + $this->assertTrue( Loader::get_loaded()['plugins']['wordfence'] ); + } +} diff --git a/tests/customers/tests-customer-addresses.php b/tests/customers/tests-customer-addresses.php new file mode 100644 index 00000000000..f403b5afe2a --- /dev/null +++ b/tests/customers/tests-customer-addresses.php @@ -0,0 +1,786 @@ +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..d1c8c9e5b6d --- /dev/null +++ b/tests/customers/tests-customer-email-addresses.php @@ -0,0 +1,448 @@ +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 ); + } + + /** + * This test covers a scenario where a customer exists but does not have an email address assigned. + * This could have happened during the migration, if a customer had no orders which could be migrated. + */ + public function test_get_customer_by_email_missing_email_returns_customer() { + $customer_id = edd_add_customer( + array( + 'name' => 'Test Customer', + 'email' => 'missing@edd.test', + ) + ); + + // EDD does add the email, so we need to delete it to shim the missing email experience. + $emails = edd_get_customer_email_addresses( + array( + 'customer_id' => $customer_id, + ) + ); + foreach ( $emails as $email ) { + edd_delete_customer_email_address( $email->id ); + } + $this->assertEmpty( edd_get_customer_email_addresses( array( + 'customer_id' => $customer_id, + ) ) ); + + $customer = edd_get_customer_by( 'email', 'missing@edd.test' ); + $this->assertEquals( $customer_id, $customer->id ); + $this->assertSame( 'missing@edd.test', $customer->email ); + } +} 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..9ae30457cec --- /dev/null +++ b/tests/customers/tests-customers-db.php @@ -0,0 +1,380 @@ +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() ); + $this->assertSame( 5, edd_count_customers( array( 'fields' => 'ids' ) ) ); + } + + 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_values( explode( ',', $customer->payment_ids ) ); + $order_ids = $customer->order_ids; + + $this->assertTrue( in_array( $payment_id, $payment_ids ) ); + $this->assertTrue( in_array( $payment_id, $order_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 ); + } + + public function test_updating_customer_with_empty_email_is_false() { + $customer = edd_get_customer( self::$customers[0] ); + + $this->assertFalse( + edd_update_customer( + self::$customers[0], + array( + 'email' => '', + ) + ) + ); + } + + /** + * @expectedDeprecated EDD\Deprecated\EmptyClass->non_existent_property + */ + public function test_edd_customer_db_empty_class_is_deprecated() { + $this->assertTrue( class_exists( 'EDD_DB_Customers' ) ); + $this->assertTrue( class_exists( 'EDD_DB_Customer_Meta' ) ); + + $deprecated = new \EDD_DB_Customers(); + $this->assertTrue( $deprecated instanceof \EDD\Deprecated\EmptyClass ); + $this->assertNull( $deprecated->non_existent_property ); + } +} diff --git a/tests/customers/tests-customers-views.php b/tests/customers/tests-customers-views.php new file mode 100644 index 00000000000..620a4990b9b --- /dev/null +++ b/tests/customers/tests-customers-views.php @@ -0,0 +1,63 @@ +assertSame( + array( + 'customers' => esc_html__( 'Customers', 'easy-digital-downloads' ), + 'emails' => esc_html__( 'Email Addresses', 'easy-digital-downloads' ), + 'physical' => esc_html__( 'Physical Addresses', 'easy-digital-downloads' ), + ), + \edd_get_customer_pages() + ); + } + + /** + * @covers ::edd_customer_views + */ + public function test_registered_views() { + $this->assertSame( + array( + 'overview' => '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', + ), + \edd_customer_views() + ); + } + + /** + * @covers ::edd_customer_tabs + */ + public function test_registered_tabs() { + $this->assertSame( + 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' ) ), + 'delete' => array( 'dashicon' => 'dashicons-trash', 'title' => _x( 'Delete', 'Delete Customer tab title', 'easy-digital-downloads' ) ), + ), + \edd_customer_tabs() + ); + } +} diff --git a/tests/customers/tests-customers.php b/tests/customers/tests-customers.php new file mode 100644 index 00000000000..6d11a24aad9 --- /dev/null +++ b/tests/customers/tests-customers.php @@ -0,0 +1,514 @@ +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->assertEquals( $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 ) ); + } + + /** + * @expectedDeprecated EDD_Customer::get_payment_ids + */ + 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() ); + } + + /** + * @expectedDeprecated EDD_Customer::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_remove_email_for_customer_cannot_remove_other_email() { + self::$customers[1]->add_email( 'added-email@edd.test' ); + + // Verify the email is on the customer. + $this->assertTrue( in_array( 'added-email@edd.test', self::$customers[1]->emails, true ) ); + + // Try and remove it from a different customer. + $this->assertFalse( self::$customers[2]->remove_email( 'added-email@edd.test' ) ); + $this->assertTrue( 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_edd_has_purchases_pending_orders_should_return_false() { + edd_update_order( + self::$order, + array( + 'status' => 'pending', + ) + ); + self::$customers[0]->attach_payment( self::$order ); + + $this->assertFalse( 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 ); + } + + /** + * @expectedDeprecated EDD_Customer::get_payment_ids + */ + 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() ); + } + + /** + * @expectedDeprecated EDD_Customer::get_payments + */ + 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_orders_with_invalid_customer_should_be_empty() { + $customer = new \EDD_Customer( 'test_user@example.com' ); + + $this->assertEmpty( $customer->get_orders() ); + } + + public function test_get_orders_valid() { + $customer = self::$customers[0]; + + $this->assertNotEmpty( $customer->get_orders() ); + } + + 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'] ); + } + + /** + * @expectedDeprecated EDD_Customer::get_payment_ids + */ + public function test_get_customer_payment_ids_should_return_1() { + $this->assertCount( 1, self::$customers[0]->get_payment_ids() ); + } + + /** + * @expectedDeprecated EDD_Customer::get_payments + */ + public function test_get_customer_payments_should_return_1() { + // create some extra orders + $orders = parent::edd()->order->create_many( 5 ); + $customer = reset( self::$customers ); + $payments = $customer->get_payments(); + $this->assertCount( 1, $payments ); + + $payment = reset( $payments ); + $this->assertEquals( $customer->id, $payment->customer_id ); + } + + /** + * @expectedDeprecated EDD_Customer::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 ); + } + + public function test_creating_duplicate_customer_returns_original_customer_id() { + $test_email = 'newcustomer@edd.local'; + + $customer_id = edd_add_customer( array( 'email' => $test_email ) ); + + $customer = new \EDD_Customer( 0 ); + $potential_duplicate_customer_id = $customer->create( array( 'email' => $test_email ) ); + + $this->assertEquals( $customer_id, $potential_duplicate_customer_id ); + } +} diff --git a/tests/dates/tests-date-functions.php b/tests/dates/tests-date-functions.php new file mode 100644 index 00000000000..e60be4aec8e --- /dev/null +++ b/tests/dates/tests-date-functions.php @@ -0,0 +1,322 @@ +utils->get_gmt_offset( true ); + } + + public function tearDown(): void { + $_REQUEST['range'] = ''; + + parent::tearDown(); + } + + 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_with_offset() { + $this->assertSame( 'GMT-05:00', edd_get_timezone_id( true ) ); + } + + /** + * @covers ::edd_get_timezone_id() + */ + public function test_get_timezone_should_return_the_current_timezone_based_on_WP_settings() { + update_option( 'timezone_string', 'America/Chicago' ); + + $this->assertSame( 'America/Chicago', edd_get_timezone_id() ); + + delete_option( 'timezone_string' ); + } + + /** + * @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 ); + } + + /** + * @covers ::edd_get_utc_date_string() + */ + public function test_get_utc_date_string_from_local() { + $local_date = '2020-01-10 13:09:00'; + $utc_date = edd_get_utc_date_string( $local_date ); + + $this->assertSame( '2020-01-10 18:09:00', $utc_date ); + } + + /** + * @covers ::edd_get_utc_date_string()ß + */ + public function test_get_utc_date_string_from_local_with_possitive_offset() { + update_option( 'gmt_offset', +5 ); + + $local_date = '2020-01-10 13:09:00'; + $utc_date = edd_get_utc_date_string( $local_date, 'Y-m-d H:i:s' ); + + $this->assertSame( '2020-01-10 08:09:00', $utc_date ); + } + + /** + * @covers ::edd_get_utc_date_string() + */ + public function test_get_utc_date_string_from_local_with_zero_offset() { + update_option( 'gmt_offset', 0 ); + + $local_date = '2020-01-10 13:09:00'; + $utc_date = edd_get_utc_date_string( $local_date, 'Y-m-d H:i:s' ); + + $this->assertSame( '2020-01-10 13:09:00', $utc_date ); + } +} diff --git a/tests/dates/tests-date-utilities.php b/tests/dates/tests-date-utilities.php new file mode 100644 index 00000000000..3be523d9315 --- /dev/null +++ b/tests/dates/tests-date-utilities.php @@ -0,0 +1,185 @@ +utils->get_gmt_offset( true ); + } + + public function test_Date_should_extend_DateTime() { + $this->assertInstanceOf( 'DateTime', $this->get_date_instance() ); + } + + 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 ) ); + + $this->assertSame( $expected, $date->format( 'mysql' ) ); + } + + 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 ) ); + + $this->assertSame( $expected, $date->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 ) ); + + $this->assertSame( $expected, $date->format( true ) ); + } + + 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 ) ); + + $this->assertSame( $expected, $date->format( 'date' ) ); + } + + 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 ) ); + + $this->assertSame( $expected, $date->format( 'time' ) ); + } + + 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 ) ); + + $this->assertSame( $expected, $date->format( 'mysql' ) ); + } + + public function test_format_object_should_return_Date_object() { + $date = $this->get_date_instance(); + + $this->assertEquals( $date, $date->format( 'object' ) ); + } + + public function test_format_timestamp_should_return_original_timestamp() { + $date = $this->get_date_instance(); + + $this->assertSame( strtotime( self::$date_string ), $date->format( 'timestamp' ) + EDD()->utils->get_gmt_offset() ); + } + + public function test_format_wp_timestamp_should_return_WP_timestamp() { + $date = $this->get_date_instance(); + $expected = strtotime( self::$date_string ); + + $this->assertSame( $expected, $date->format( 'wp_timestamp' ) ); + } + + 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 ) ); + + $this->assertSame( $expected, $date->format( 'm/d/Y' ) ); + } + + public function test_getWPTimestamp_should_return_timestamp_with_offset_applied() { + $date = $this->get_date_instance(); + $expected = strtotime( self::$date_string ); + + $this->assertSame( $expected, $date->getWPTimestamp() ); + } + + public function test_date_utility_matches_local_time_new_york() { + $dates = $this->get_dates( 'America/New_York' ); + + $this->assertEquals( $dates['local_time'], $dates['new_utils_date'] ); + $this->assertEquals( $dates['local_time'], $dates['utils_date'] ); + } + + public function test_date_utility_matches_local_time_fiji() { + $dates = $this->get_dates( 'Pacific/Fiji' ); + + $this->assertEquals( $dates['local_time'], $dates['new_utils_date'] ); + $this->assertEquals( $dates['local_time'], $dates['utils_date'] ); + } + + public function test_date_utility_matches_local_time_london() { + $dates = $this->get_dates( 'Europe/London' ); + + $this->assertEquals( $dates['local_time'], $dates['new_utils_date'] ); + $this->assertEquals( $dates['local_time'], $dates['utils_date'] ); + } + + public function test_date_utility_matches_local_time_offset() { + $dates = $this->get_dates( '', '-7.5' ); + + $this->assertEquals( $dates['local_time'], $dates['new_utils_date'] ); + $this->assertEquals( $dates['local_time'], $dates['utils_date'] ); + } + + public function test_date_utility_matches_utc_empty_offset() { + $dates = $this->get_dates( 'UTC', '' ); + + $this->assertEquals( $dates['local_time'], $dates['new_utils_date'] ); + $this->assertEquals( $dates['local_time'], $dates['utils_date'] ); + } + + public function test_date_utility_with_localize_string() { + $timezone_format = 'Y-m-d H:i:00'; + $new_utils_date = new \EDD\Utils\Date( 'now' ); + $utils_date = EDD()->utils->date( 'now', null, 'invalid localize value' ); + $local_time = date_i18n( $timezone_format ); + + $this->assertEquals( $local_time, $new_utils_date->format( $timezone_format ) ); + $this->assertEquals( $local_time, $utils_date->format( $timezone_format ) ); + } + + private function get_dates( $timezone_string = 'America/New_York', $gmt_offset = 0 ) { + // Update and refresh the timezone and gmt offset. + update_option( 'gmt_offset', $gmt_offset ); + update_option( 'timezone_string', $timezone_string ); + + // Set the timezone format. + $timezone_format = 'Y-m-d H:i:00'; + + // Create the date objects. + $new_utils_date = new \EDD\Utils\Date( 'now' ); + $utils_date = EDD()->utils->date( 'now', null, true ); + + return array( + 'local_time' => date_i18n( $timezone_format ), + 'new_utils_date' => $new_utils_date->format( $timezone_format ), + 'utils_date' => $utils_date->format( $timezone_format ), + ); + } + + /** + * Helper to retrieve a Date instance. + * + * @return \EDD\Utils\Date + */ + protected function get_date_instance() { + return new Date( self::$date_string ); + } +} diff --git a/tests/discounts/tests-discount-categories.php b/tests/discounts/tests-discount-categories.php new file mode 100644 index 00000000000..03f912db9b1 --- /dev/null +++ b/tests/discounts/tests-discount-categories.php @@ -0,0 +1,366 @@ +ID, self::$category, 'download_category' ); + + $child_category = wp_insert_term( + 'Test Child Category', + 'download_category', + array( + 'parent' => self::$category, + ) + ); + self::$child_category = $child_category['term_id']; + + $third_download = self::$downloads[2]; + wp_set_object_terms( $third_download->ID, self::$child_category, 'download_category' ); + } + + public function setUp(): void { + parent::setUp(); + + $discount = Helpers\EDD_Helper_Discount::create_simple_percent_discount(); + self::$discount = edd_get_discount( $discount ); + } + + public function tearDown(): void { + edd_empty_cart(); + Helpers\EDD_Helper_Discount::delete_discount( self::$discount->id ); + parent::tearDown(); + } + + public function test_update_discount_category_included() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + ) + ); + $discount = edd_get_discount( self::$discount->id ); + + $this->assertSame( array( self::$category ), $discount->get_categories() ); + $this->assertEmpty( $discount->get_term_condition() ); + } + + public function test_update_discount_category_excluded() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + 'term_condition' => 'exclude', + ) + ); + $discount = edd_get_discount( self::$discount->id ); + + $this->assertSame( array( self::$category ), $discount->get_categories() ); + $this->assertEquals( 'exclude', $discount->get_term_condition() ); + } + + public function test_update_discount_no_category_no_term() { + edd_update_discount( + self::$discount->id, + array( + 'term_condition' => 'exclude', + ) + ); + $discount = edd_get_discount( self::$discount->id ); + + $this->assertEmpty( $discount->get_term_condition() ); + } + + public function test_item_in_cart_no_category_discount_is_valid_for_categories_is_false() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + ) + ); + + $download = reset( self::$downloads ); + edd_add_to_cart( $download->ID ); + + $this->assertFalse( self::$discount->is_valid_for_categories() ); + } + + public function test_edd_validate_discount_no_category_is_false() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + ) + ); + + $download = reset( self::$downloads ); + + $this->assertFalse( edd_validate_discount( self::$discount->id, $download->ID ) ); + } + + public function test_item_in_cart_category_discount_is_valid_for_categories_is_true() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + ) + ); + + $download = self::$downloads[1]; + edd_add_to_cart( $download->ID ); + + $this->assertTrue( self::$discount->is_valid_for_categories() ); + } + + public function test_item_in_cart_category_discount_is_valid_for_categories_is_false() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + 'term_condition' => 'exclude', + ) + ); + + $download = self::$downloads[1]; + edd_add_to_cart( $download->ID ); + + $this->assertFalse( self::$discount->is_valid_for_categories() ); + } + + public function test_item_in_cart_category_discount_is_valid_for_categories_child_category_is_true() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + ) + ); + + $download = self::$downloads[2]; + edd_add_to_cart( $download->ID ); + + $this->assertTrue( self::$discount->is_valid_for_categories() ); + } + + public function test_item_in_cart_category_discount_is_valid_for_categories_child_category_is_false() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + 'term_condition' => 'exclude', + ) + ); + + $download = self::$downloads[2]; + edd_add_to_cart( $download->ID ); + + $this->assertFalse( self::$discount->is_valid_for_categories() ); + } + + public function test_edd_validate_discount_category_is_true() { + edd_update_discount( + self::$discount->id, + array( + 'categories' => array( self::$category ), + ) + ); + + $download = self::$downloads[1]; + + $this->assertTrue( edd_validate_discount( self::$discount->id, $download->ID ) ); + } + + public function test_get_cart_item_discount_amount_not_in_category_is_0() { + edd_update_discount( + self::$discount->id, + array( + 'min_charge_amount' => 0, + 'categories' => array( self::$category ), + ) + ); + + $download = self::$downloads[0]; + edd_add_to_cart( $download->ID ); + $cart_contents = edd_get_cart_contents(); + + $this->assertEquals( 0, edd_get_item_discount_amount( reset( $cart_contents ), $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_cart_item_discount_amount_in_category_is_4() { + edd_update_discount( + self::$discount->id, + array( + 'min_charge_amount' => 0, + 'categories' => array( self::$category ), + ) + ); + + $download = self::$downloads[1]; + edd_add_to_cart( $download->ID ); + $cart_contents = edd_get_cart_contents(); + + $this->assertEquals( 4, edd_get_item_discount_amount( reset( $cart_contents ), $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_cart_item_discount_amount_not_in_excluded_category_is_4() { + edd_update_discount( + self::$discount->id, + array( + 'min_charge_amount' => 0, + 'categories' => array( self::$category ), + 'term_condition' => 'exclude', + ) + ); + + $download = self::$downloads[0]; + edd_add_to_cart( $download->ID ); + $cart_contents = edd_get_cart_contents(); + + $this->assertEquals( 4, edd_get_item_discount_amount( reset( $cart_contents ), $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_cart_item_discount_amount_in_excluded_category_is_0() { + edd_update_discount( + self::$discount->id, + array( + 'min_charge_amount' => 0, + 'categories' => array( self::$category ), + 'term_condition' => 'exclude', + ) + ); + + $download = self::$downloads[1]; + edd_add_to_cart( $download->ID ); + $cart_contents = edd_get_cart_contents(); + + $this->assertEquals( 0, edd_get_item_discount_amount( reset( $cart_contents ), $cart_contents, array( self::$discount->code ) ) ); + } + + /** + * Test a discount that requires a product which is excluded by its category. + * + * @return void + */ + public function test_edd_validate_discount_product_requirements_but_excluded_category() { + $download = self::$downloads[1]; + $args = array( + 'product_reqs' => array( $download->ID ), + 'product_condition' => 'all', + 'max_uses' => 10000, + 'categories' => array( self::$category ), + 'term_condition' => 'exclude', + 'min_charge_amount' => 0, + 'scope' => 'global', + ); + edd_update_discount( self::$discount->id, $args ); + edd_add_to_cart( $download->ID ); + + $discount = edd_get_discount( self::$discount->id ); + $cart_contents = edd_get_cart_contents(); + $this->assertTrue( $discount->is_product_requirements_met( false ) ); + $this->assertFalse( $discount->is_valid_for_categories( false ) ); + $this->assertEquals( 4.0, edd_get_item_discount_amount( reset( $cart_contents ), $cart_contents, array( self::$discount->code ) ) ); + } + + /** + * Test a discount for a product requirement that allows other items in the cart to be discounted. + * + * @return void + */ + public function test_edd_validate_discount_product_requirements_but_excluded_category_two_products() { + $download = self::$downloads[1]; + $args = array( + 'product_reqs' => array( $download->ID ), + 'product_condition' => 'all', + 'max_uses' => 10000, + 'categories' => array( self::$category ), + 'term_condition' => 'exclude', + 'min_charge_amount' => 0, + 'scope' => 'global', + ); + edd_update_discount( self::$discount->id, $args ); + edd_add_to_cart( $download->ID ); + + $download_2 = self::$downloads[0]; + edd_add_to_cart( $download_2->ID ); + + $discount = edd_get_discount( self::$discount->id ); + $cart_contents = edd_get_cart_contents(); + $this->assertTrue( $discount->is_product_requirements_met( false ) ); + $this->assertTrue( $discount->is_valid_for_categories( false ) ); + $this->assertEquals( 4.0, edd_get_item_discount_amount( reset( $cart_contents ), $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 4.0, edd_get_item_discount_amount( $cart_contents[1], $cart_contents, array( self::$discount->code ) ) ); + } + + /** + * Test a discount for a product requirement to be discounted and an excluded category to not be discounted. + * + * @return void + */ + public function test_edd_validate_discount_one_product_requirement_one_excluded_category() { + $download_1 = self::$downloads[0]; + $download_2 = self::$downloads[1]; + $args = array( + 'product_reqs' => array( $download_1->ID ), + 'product_condition' => 'all', + 'categories' => array( self::$category ), + 'term_condition' => 'exclude', + 'min_charge_amount' => 0, + 'scope' => 'global', + ); + + edd_update_discount( self::$discount->id, $args ); + edd_add_to_cart( $download_1->ID ); + edd_add_to_cart( $download_2->ID ); + + $discount = edd_get_discount( self::$discount->id ); + $cart_contents = edd_get_cart_contents(); + + $this->assertTrue( $discount->is_product_requirements_met( false ) ); + $this->assertFalse( $discount->is_valid_for_categories( false, array( $download_2->ID ) ) ); + $this->assertEquals( 4.0, edd_get_item_discount_amount( $cart_contents[0], $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 0, edd_get_item_discount_amount( $cart_contents[1], $cart_contents, array( self::$discount->code ) ) ); + } +} diff --git a/tests/discounts/tests-discount-meta.php b/tests/discounts/tests-discount-meta.php new file mode 100644 index 00000000000..24d8674dc04 --- /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-discount-statuses.php b/tests/discounts/tests-discount-statuses.php new file mode 100644 index 00000000000..241381709e6 --- /dev/null +++ b/tests/discounts/tests-discount-statuses.php @@ -0,0 +1,106 @@ +assertTrue( self::$discount->is_active( false, false ) ); + } + + /** + * @covers \EDD_Discount::is_active() + */ + public function test_discount_is_active_with_update() { + $this->assertTrue( self::$discount->is_active( true, false ) ); + } + + /** + * @covers \EDD_Discount::is_active() + */ + public function test_discount_is_not_active_with_update_expired() { + // Directly update the end_date with a date in the past, to avoid update logic from triggering. + self::$discount->__set( 'end_date', date( 'Y-m-d', time() - DAY_IN_SECONDS ) ); + + $this->assertFalse( self::$discount->is_active( true, false ) ); + } + + /** + * @covers \EDD_Discount::is_active() + */ + public function test_discount_is_not_active_with_update_not_started() { + // Directly update the start_date with a date in the future, to avoid update logic from triggering. + self::$discount->__set( 'start_date', date( 'Y-m-d', time() + DAY_IN_SECONDS ) ); + + $this->assertFalse( self::$discount->is_active( true, false ) ); + } + + /** + * @covers \EDD_Discount::is_active() + */ + public function test_discount_is_not_active_when_archived() { + // Directly update the status to archived. + self::$discount->__set( 'status', 'archived' ); + + $this->assertFalse( self::$discount->is_active( true, false ) ); + } + + /** + * @covers \EDD_Discount::is_archived() + */ + public function test_discount_is_archived() { + // Directly update the status to archived. + self::$discount->__set( 'status', 'archived' ); + + $this->assertTrue( self::$discount->is_archived() ); + } + + /** + * @covers \EDD_Discount::is_archived() + */ + public function test_discount_is_not_archived() { + $this->assertFalse( self::$discount->is_archived() ); + } + +} diff --git a/tests/discounts/tests-discounts-db.php b/tests/discounts/tests-discounts-db.php new file mode 100644 index 00000000000..c30a70759b5 --- /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-uses.php b/tests/discounts/tests-discounts-uses.php new file mode 100644 index 00000000000..59e5d790e9e --- /dev/null +++ b/tests/discounts/tests-discounts-uses.php @@ -0,0 +1,199 @@ + 54 ) ); + $this->assertEquals( 54, $this->get_discount()->uses ); + $this->assertNotEmpty( $this->get_discount()->end_date ); + } + + /** + * @covers ::get_uses() + */ + public function test_get_discount_uses_by_method() { + edd_update_discount( self::$discount_id, array( 'use_count' => 54 ) ); + $this->assertEquals( 54, $this->get_discount()->get_uses() ); + } + + /** + * @covers ::get_max_uses() + */ + public function test_get_discount_max_uses_by_property() { + edd_update_discount( self::$discount_id, array( 'max_uses' => 10 ) ); + $this->assertEquals( 10, $this->get_discount()->max_uses ); + } + + /** + * @covers ::get_max_uses() + */ + public function test_get_discount_max_uses_by_method() { + edd_update_discount( self::$discount_id, array( 'max_uses' => 10 ) ); + $this->assertEquals( 10, $this->get_discount()->get_max_uses() ); + } + + /** + * @covers \edd_is_discount_maxed_out() + */ + public function test_discount_not_maxed_out_no_max_helper_function() { + $this->assertFalse( edd_is_discount_maxed_out( self::$discount_id ) ); + } + + /** + * @covers \EDD_Discount::is_maxed_out() + */ + public function test_discount_not_maxed_out_no_max_class_method() { + $this->assertFalse( $this->get_discount()->is_maxed_out() ); + } + + /** + * @covers \edd_is_discount_maxed_out() + */ + public function test_discount_not_maxed_out_has_max_helper_function() { + edd_update_discount( self::$discount_id, array( 'max_uses' => 10 ) ); + $this->assertFalse( edd_is_discount_maxed_out( self::$discount_id ) ); + } + + /** + * @covers \EDD_Discount::is_maxed_out() + */ + public function test_discount_not_maxed_out_has_max_class_method() { + edd_update_discount( self::$discount_id, array( 'max_uses' => 10 ) ); + $this->assertFalse( $this->get_discount()->is_maxed_out() ); + } + + /** + * @covers \edd_is_discount_maxed_out() + */ + public function test_discount_not_maxed_out_has_max_and_uses_helper_function() { + edd_update_discount( self::$discount_id, array( 'max_uses' => 10, 'use_count' => 1 ) ); + $this->assertFalse( edd_is_discount_maxed_out( self::$discount_id ) ); + } + + /** + * @covers \EDD_Discount::is_maxed_out() + */ + public function test_discount_not_maxed_out_has_max_and_uses_class_method() { + edd_update_discount( self::$discount_id, array( 'max_uses' => 10, 'use_count' => 1 ) ); + $this->assertFalse( $this->get_discount()->is_maxed_out() ); + } + + /** + * @covers \edd_is_discount_maxed_out() + */ + public function test_discount_maxed_out_has_max_and_uses_helper_function() { + edd_update_discount( self::$discount_id, array( 'max_uses' => 10, 'use_count' => 10 ) ); + $this->assertTrue( edd_is_discount_maxed_out( self::$discount_id ) ); + } + + /** + * @covers \EDD_Discount::is_maxed_out() + */ + public function test_discount_maxed_out_has_max_and_uses_class_method() { + edd_update_discount( self::$discount_id, array( 'max_uses' => 10, 'use_count' => 10 ) ); + + // Since we're not updating the discount object, we need to re-fetch it. + $discount = edd_get_discount( self::$discount_id ); + $this->assertTrue( $discount->is_maxed_out() ); + } + + /** + * @covers \edd_delete_discount() + */ + public function test_deletion_of_discount_should_be_false_because_use_count_not_0() { + edd_update_discount( self::$discount_id, array( 'use_count' => 1 ) ); + edd_delete_discount( self::$discount_id ); + + $this->assertInstanceOf( 'EDD_Discount', $this->get_discount() ); + } + + + /** + * @covers \edd_delete_discount() + */ + public function test_deletion_of_discount_should_be_true_because_use_count_not_0() { + edd_update_discount( self::$discount_id, array( 'use_count' => 0 ) ); + edd_delete_discount( self::$discount_id ); + + $this->assertFalse( $this->get_discount() ); + } + + /** + * @covers \edd_decrease_discount_usage() + */ + public function test_decrease_discount_usage() { + // We create this as 0, so we need to set it to 1. + edd_update_discount( self::$discount_id, array( 'use_count' => 1 ) ); + $uses = edd_get_discount_uses( self::$discount_id ); + + $decreased = edd_decrease_discount_usage( $this->get_discount()->code ); + $this->assertSame( $decreased, (int) $uses - 1 ); + } + + /** + * @covers \edd_increase_discount_usage() + */ + public function test_increase_discount_usage() { + $uses = edd_get_discount_uses( self::$discount_id ); + + $increased = edd_increase_discount_usage( $this->get_discount()->code ); + $this->assertSame( $increased, (int) $uses + 1 ); + } + + public function test_getting_discount_usage_of_invalid_discount_is_false() { + // Test missing codes + $this->assertFalse( edd_decrease_discount_usage( 'INVALIDDISCOUNTCODE' ) ); + } + + /** + * Just a wrapper to get the discount object. + */ + private function get_discount() { + return edd_get_discount( self::$discount_id ); + } +} diff --git a/tests/discounts/tests-discounts.php b/tests/discounts/tests-discounts.php new file mode 100755 index 00000000000..e38806fd312 --- /dev/null +++ b/tests/discounts/tests-discounts.php @@ -0,0 +1,1389 @@ +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_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 ); + } + + public function test_get_discount_categories_by_method() { + $this->assertSame( array(), self::$discount->get_categories() ); + } + + public function test_get_discount_categories_by_property() { + $this->assertSame( array(), self::$discount->categories ); + } + + public function test_get_discount_term_condition_by_method() { + $this->assertSame( '', self::$discount->get_term_condition() ); + } + + public function test_get_discount_term_condition_by_property() { + $this->assertSame( '', self::$discount->term_condition ); + } + + /** + * @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 ::update_status() + * @covers ::get_status() + */ + public function test_discount_update_status_to_archived() { + self::$discount->update_status( 'archived' ); + + $this->assertEquals( 'archived', 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 ); + + $discount = edd_get_discount( self::$discount_id ); + + $this->assertTrue( $discount->is_product_requirements_met() ); + } + + /** + * @covers ::is_product_requirements_met() with variable download + * @covers edd_validate_discount() with variable download + */ + public function test_discount_is_product_requirements_met_with_variable_download() { + $variable_download_id = Helpers\EDD_Helper_Download::create_variable_download(); + $variable_download = edd_get_download( $variable_download_id->ID ); + $price_id = 0; + + $args = array( + 'product_reqs' => array( $variable_download->ID . '_' . $price_id ), + ); + + edd_update_discount( self::$discount_id, $args ); + + edd_add_to_cart( $variable_download->ID, array( 'price_id' => $price_id ) ); + + $discount = edd_get_discount( self::$discount_id ); + + $this->assertTrue( $discount->is_product_requirements_met() ); + $this->assertTrue( edd_validate_discount( self::$discount_id, array( $variable_download->ID . '_' . $price_id ) ) ); + } + + public function test_discount_is_product_requirements_met_with_variable_download_all_variations() { + $variable_download_id = Helpers\EDD_Helper_Download::create_variable_download(); + $variable_download = edd_get_download( $variable_download_id->ID ); + $price_id = 0; + + $args = array( + 'product_reqs' => array( $variable_download->ID ), + ); + + edd_update_discount( self::$discount_id, $args ); + + edd_add_to_cart( $variable_download->ID, array( 'price_id' => $price_id ) ); + + $discount = edd_get_discount( self::$discount_id ); + + $this->assertTrue( $discount->is_product_requirements_met() ); + $this->assertTrue( edd_validate_discount( self::$discount_id, array( $variable_download->ID . '_' . $price_id ) ) ); + } + + + /** + * @covers ::is_product_requirements_met() with variable download + * @covers edd_validate_discount() with variable download + */ + public function test_discount_is_product_requirements_met_with_variable_download_is_false() { + $variable_download_id = Helpers\EDD_Helper_Download::create_variable_download(); + $variable_download = edd_get_download( $variable_download_id->ID ); + $price_id = 0; + + $args = array( + 'product_reqs' => array( $variable_download->ID . '_' . $price_id ), + ); + + edd_update_discount( self::$discount_id, $args ); + + edd_add_to_cart( $variable_download->ID, array( 'price_id' => 1 ) ); + + $discount = edd_get_discount( self::$discount_id ); + + $this->assertFalse( $discount->is_product_requirements_met() ); + $this->assertFalse( edd_validate_discount( self::$discount_id, array( $variable_download->ID . '_1' ) ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_product_requirements_any_all_in_array() { + $products = array( self::$download->ID, 99999 ); + $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, 99999 ); + $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, 99999 ); + $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, 99999 ); + $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, 99999 ); + $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, 99999 ); + $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, 99999 ); + $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, 99999 ); + $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, 99999 ); + $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_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_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_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_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_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.00', 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.00', 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 ) ); + } + + public function test_edd_validate_discount_product_requirements_all_all_variations_in_array() { + $variable_download_id = Helpers\EDD_Helper_Download::create_variable_download(); + $variable_download = edd_get_download( $variable_download_id->ID ); + $products = array( self::$download->ID, $variable_download->ID ); + $args = array( + 'product_reqs' => array( self::$download->ID ), + 'product_condition' => 'all', + 'max_uses' => 10000, + ); + edd_update_discount( self::$discount_id, $args ); + edd_add_to_cart( $variable_download->ID, array( 'price_id' => array( 0, 1 ) ) ); + + $discount = edd_get_discount( self::$discount_id ); + $this->assertFalse( $discount->is_product_requirements_met( false ) ); + } + + /** + * Tests a discount for a single price product when a false price ID is added to the cart. + */ + public function test_discount_product_requirements_false_price_id() { + $args = array( + 'product_reqs' => array( self::$download->ID ), + 'product_condition' => 'all', + 'max_uses' => 10000, + 'scope' => 'not_global', + ); + edd_update_discount( self::$discount_id, $args ); + edd_add_to_cart( self::$download->ID, array( 'price_id' => false ) ); + + $cart_contents = edd_get_cart_contents(); + $first_item = reset( $cart_contents ); + $discount = edd_get_discount( self::$discount_id ); + $this->assertTrue( $discount->is_product_requirements_met( false ) ); + $this->assertEquals( 4.0, edd_get_item_discount_amount( $first_item, $cart_contents, array( $discount ) ) ); + } + + /** + * Tests a discount for a single price product when an empty string price ID is added to the cart. + */ + public function test_discount_product_requirements_empty_string_price_id() { + $args = array( + 'product_reqs' => array( self::$download->ID ), + 'product_condition' => 'all', + 'max_uses' => 10000, + ); + edd_update_discount( self::$discount_id, $args ); + edd_add_to_cart( self::$download->ID, array( 'price_id' => '' ) ); + + $discount = edd_get_discount( self::$discount_id ); + $this->assertTrue( $discount->is_product_requirements_met( false ) ); + } + + public function test_store_discount_empty_start_end_is_empty() { + $time = time(); + $data = array( + 'code' => 'EXP' . (string) $time, + 'uses' => 703, + 'max_uses' => '', + 'amount' => 15, + 'start' => '', + 'expiration' => '', + 'type' => 'percent', + 'min_price' => '', + 'name' => 'Expiration Testing', + ); + + $discount_id = edd_store_discount( $data ); + $discount = edd_get_adjustment( $discount_id ); + + $this->assertEmpty( $discount->start_date ); + $this->assertEmpty( $discount->end_date ); + } + + public function test_discount_with_expiration_keeps_expiration_after_update() { + // Create a discount with an expiration date. + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount(); + edd_update_adjustment( + $discount_id, + array( + 'status' => 'inactive', + ) + ); + $discount = edd_get_discount( $discount_id ); + + $this->assertNotEmpty( $discount->end_date ); + $this->assertEquals( 'inactive', $discount->status ); + $this->assertNotEmpty( $discount->start_date ); + } + + /** + * @covers EDD_Discount::is_valid() with archived status + */ + public function test_discount_is_valid_with_archived_status_returns_false() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_adjustment( + $discount_id, + array( + 'status' => 'archived', + ) + ); + edd_add_to_cart( self::$download->ID ); + + $discount = edd_get_discount( $discount_id ); + + $this->assertFalse( $discount->is_valid() ); + } + + /** + * @covers EDD_Discount::is_valid() with not started date + */ + public function test_discount_is_valid_with_non_started_status_returns_false() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount = edd_get_discount( $discount_id ); + + $discount->__set( 'start_date', date( 'Y-m-d', time() + DAY_IN_SECONDS ) ); + + edd_add_to_cart( self::$download->ID ); + + $this->assertFalse( $discount->is_valid() ); + } + + /** + * @covers EDD_Discount::is_valid() with non-active status + */ + public function test_discount_is_valid_with_non_active_status_returns_false() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount = edd_get_discount( $discount_id ); + + $discount->__set( 'end_date', date( 'Y-m-d', time() - DAY_IN_SECONDS ) ); + + edd_add_to_cart( self::$download->ID ); + + $this->assertFalse( $discount->is_valid() ); + } + + /** + * @covers EDD_Discount::is_valid() with maxed out uses + */ + public function test_discount_is_valid_with_maxed_out_uses_returns_false() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_adjustment( + $discount_id, + array( + 'use_count' => 10, + 'max_uses' => 10, + ) + ); + edd_add_to_cart( self::$download->ID ); + + $discount = edd_get_discount( $discount_id ); + + $this->assertFalse( $discount->is_valid() ); + } + + /** + * @covers EDD_Discount::is_valid() with once per customer + */ + public function test_discount_is_valid_with_used_returns_false() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_adjustment( + $discount_id, + array( + 'once_per_customer' => true, + ) + ); + $discount = edd_get_discount( $discount_id ); + + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment(); + $payment = edd_get_payment( $payment_id ); + $payment->discounts = $discount->get_code(); + $payment->status = 'publish'; + $payment->save(); + + edd_add_to_cart( self::$download->ID ); + + $this->assertFalse( $discount->is_valid( 'admin@example.org' ) ); + + Helpers\EDD_Helper_Payment::delete_payment( $payment_id ); + } + + /** + * @covers EDD_Discount::is_valid() with invalid product requirements + */ + public function test_discount_is_valid_with_product_requirements_returns_false() { + $download_1 = Helpers\EDD_Helper_Download::create_simple_download(); + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + + edd_update_discount( + $discount_id, + array( + 'product_reqs' => array( $download_1->ID ), + 'product_condition' => 'all', + ) + ); + edd_add_to_cart( self::$download->ID ); + + $discount = edd_get_discount( $discount_id ); + + $this->assertFalse( $discount->is_valid() ); + } + + /** + * @covers EDD_Discount::is_valid() with excluded category + */ + public function test_discount_is_valid_with_excluded_category_returns_false() { + $category_1 = wp_insert_term( 'Test Category', 'download_category' ); + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + + edd_update_discount( + $discount_id, + array( + 'categories' => array( $category_1['term_id'] ), + 'term_condition' => 'exclude', + ) + ); + wp_set_object_terms( self::$download->ID, $category_1['term_id'], 'download_category' ); + edd_add_to_cart( self::$download->ID ); + + $discount = edd_get_discount( $discount_id ); + + $this->assertFalse( $discount->is_valid() ); + } + + /** + * @covers EDD_Discount::is_valid() + */ + public function test_discount_is_valid_simple_returns_true() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_add_to_cart( self::$download->ID ); + + $discount = edd_get_discount( $discount_id ); + + $this->assertTrue( $discount->is_valid() ); + } +} diff --git a/tests/discounts/tests-generator.php b/tests/discounts/tests-generator.php new file mode 100644 index 00000000000..5e6ae3f657b --- /dev/null +++ b/tests/discounts/tests-generator.php @@ -0,0 +1,112 @@ +markTestSkipped( 'This test requires EDD Pro.' ); + } + parent::setUp(); + } + + /** + * Runs after each test method. + */ + public function tearDown(): void { + parent::tearDown(); + + // Remove all adjustments after each test. + edd_get_component_interface( 'adjustment', 'table' )->truncate(); + } + + /** + * Test that the generator can generate a valid code. + */ + public function test_generate_valid_code() { + $code = self::$generator->generate(); + + $this->assertNotEmpty( $code ); + $this->assertIsString( $code ); + $this->assertLessThanOrEqual( 50, strlen( $code ) ); + $this->assertGreaterThanOrEqual( 6, strlen( $code ) ); + } + + /** + * Test that the generator can generate a valid code with a prefix. + */ + public function test_generate_code_with_prefix() { + $code = self::$generator->generate( 'TestPrefix-' ); + $this->assertStringStartsWith( 'TestPrefix-', $code ); + } + + /** + * Test that the generator can generate a valid code with a prefix and length. + */ + public function test_generate_code_with_prefix_and_length() { + $code = self::$generator->generate( 'Test-', 'hash', 10 ); + $this->assertEquals( 15, strlen( $code ) ); + } + + /** + * Test that the generator can generate a valid code with a prefix and length. + */ + public function test_generate_code_with_prefix_and_length_too_long() { + $code = self::$generator->generate( 'Test-', 'hash', 100 ); + + $this->assertEquals( 50, strlen( $code ) ); + } + + public function test_generate_code_only_letters() { + $code = self::$generator->generate( '', 'letters', 10 ); + + $this->assertRegExp( '/^[A-Z]+$/', $code ); + $this->assertEquals( 10, strlen( $code ) ); + } + + public function test_generate_code_only_numbers() { + $code = self::$generator->generate( '', 'numbers', 10 ); + + $this->assertRegExp( '/^[0-9]+$/', $code ); + $this->assertEquals( 10, strlen( $code ) ); + } + + public function test_generate_code_too_short() { + $code = self::$generator->generate( '', 'hash', 1 ); + + $this->assertEquals( 6, strlen( $code ) ); + } + + public function test_generate_code_too_long() { + $code = self::$generator->generate( '', 'hash', 1000 ); + + $this->assertEquals( 50, strlen( $code ) ); + } +} diff --git a/tests/discounts/tests-has-active-discounts.php b/tests/discounts/tests-has-active-discounts.php new file mode 100644 index 00000000000..70bf6341507 --- /dev/null +++ b/tests/discounts/tests-has-active-discounts.php @@ -0,0 +1,235 @@ +truncate(); + } + + /** + * Test with a single active discount, with no start or end date. + */ + public function test_single_active_discount_no_dates() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( 'start_date' => null, 'end_date' => null ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with a single inactive discount. + */ + public function test_single_inactive_discount() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( 'status' => 'inactive' ) ); + + $this->assertFalse( edd_has_active_discounts() ); + } + + /** + * Test with a single archived discount. + */ + public function test_single_archived_discount() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( 'status' => 'archived' ) ); + + $this->assertFalse( edd_has_active_discounts() ); + } + + /** + * Test with a single active discount, with a start date in the future. + */ + public function test_single_active_discount_future_start_date() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( 'start_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ) ) ); + + $this->assertFalse( edd_has_active_discounts() ); + } + + /** + * Test with a single active discount, with an end date in the past. + */ + public function test_single_active_discount_past_end_date() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( 'end_date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ) ) ); + + $this->assertFalse( edd_has_active_discounts() ); + } + + /** + * Test with a single active discount, with a start date in the past and an end date in the future. + */ + public function test_single_active_discount_past_start_date_future_end_date() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( + 'start_date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'end_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with a single active discount, with a start date in the past and an end date in the future, but the discount has hit it's max uses. + */ + public function test_single_active_discount_past_start_date_future_end_date_max_uses() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( + 'start_date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'end_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'use_count' => 10, + 'max_uses' => 10, + ) ); + + $this->assertFalse( edd_has_active_discounts() ); + } + + /** + * Test with a single active discount with a start date in the past and no end date. + */ + public function test_single_active_discount_past_start_date_no_end_date() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( + 'start_date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'end_date' => null, + ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with a single active discount with no start date and an end date in the past. + */ + public function test_single_active_discount_no_start_date_past_end_date() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( + 'start_date' => null, + 'end_date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + ) ); + + $this->assertFalse( edd_has_active_discounts() ); + } + + /** + * Test with a single active discount with no start date and an end date in the future. + */ + public function test_single_active_discount_no_start_date_future_end_date() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id, array( + 'start_date' => null, + 'end_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with two discounts, one active, one inactive. + */ + public function test_two_discounts_one_active_one_inactive() { + $discount_id_1 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount_id_2 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id_2, array( 'status' => 'inactive' ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with two discounts, one active, one archived. + */ + public function test_two_discounts_one_active_one_archived() { + $discount_id_1 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount_id_2 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id_2, array( 'status' => 'archived' ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with two discounts, one active, one expired. + */ + public function test_two_discounts_one_active_one_expired() { + $discount_id_1 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount_id_2 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id_2, array( + 'end_date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'status' => 'expired', + ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with two discounts, one active and one with a start date in the future. + */ + public function test_two_discounts_one_active_one_future_start_date() { + $discount_id_1 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount_id_2 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id_2, array( + 'start_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with two discounts one active and one with an end date in the past. + */ + public function test_two_discounts_one_active_one_past_end_date() { + $discount_id_1 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount_id_2 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount( $discount_id_2, array( + 'end_date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + ) ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * Test with two discounts, both with an end date in the past. + */ + public function test_two_discounts_both_past_end_date() { + $discount_id_1 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount_id_2 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + + $past_date = date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ); + edd_update_discount( $discount_id_1, array( 'end_date' => $past_date ) ); + edd_update_discount( $discount_id_2, array( 'end_date' => $past_date ) ); + + $this->assertFalse( edd_has_active_discounts() ); + } + + /** + * Test with two discounts, both with a start date in the future. + */ + public function test_two_discounts_both_future_start_date() { + $discount_id_1 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $discount_id_2 = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + + $future_date = date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ); + edd_update_discount( $discount_id_1, array( 'start_date' => $future_date ) ); + edd_update_discount( $discount_id_2, array( 'start_date' => $future_date ) ); + + $this->assertFalse( edd_has_active_discounts() ); + } +} diff --git a/tests/discounts/tests-item-amounts.php b/tests/discounts/tests-item-amounts.php new file mode 100644 index 00000000000..cdfb41091b7 --- /dev/null +++ b/tests/discounts/tests-item-amounts.php @@ -0,0 +1,269 @@ +ID ); + } + } + + public function test_get_item_discount_amount_single_item() { + edd_add_to_cart( self::$downloads[0]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = reset( $cart_contents ); + + $this->assertEquals( 10, edd_get_item_discount_amount( $first_item, $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_amount_two_items() { + edd_add_to_cart( self::$downloads[0]->ID ); + edd_add_to_cart( self::$downloads[1]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = reset( $cart_contents ); + $second_item = end( $cart_contents ); + + $this->assertEquals( 5, edd_get_item_discount_amount( $first_item, $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 5, edd_get_item_discount_amount( $second_item, $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_amount_three_items() { + edd_add_to_cart( self::$downloads[0]->ID ); + edd_add_to_cart( self::$downloads[1]->ID ); + edd_add_to_cart( self::$downloads[2]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = $cart_contents[0]; + $second_item = $cart_contents[1]; + $third_item = $cart_contents[2]; + + $this->assertEquals( 3.33, edd_get_item_discount_amount( $first_item, $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 3.33, edd_get_item_discount_amount( $second_item, $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 3.34, edd_get_item_discount_amount( $third_item, $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_amount_three_items_with_product_exclusion() { + edd_update_discount( self::$discount_id, array( 'excluded_products' => array( self::$downloads[1]->ID ) ) ); + + edd_add_to_cart( self::$downloads[0]->ID ); + edd_add_to_cart( self::$downloads[1]->ID ); + edd_add_to_cart( self::$downloads[2]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = $cart_contents[0]; + $second_item = $cart_contents[1]; + $third_item = $cart_contents[2]; + + $this->assertEquals( 5, edd_get_item_discount_amount( $first_item, $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 0, edd_get_item_discount_amount( $second_item, $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 5, edd_get_item_discount_amount( $third_item, $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_amount_two_variations() { + $variable_download = Helpers\EDD_Helper_Download::create_variable_download(); + edd_add_to_cart( + $variable_download->ID, + array( + 'price_id' => array( 0, 1 ), + ) + ); + + $cart_contents = edd_get_cart_contents(); + $first_item = $cart_contents[0]; + $second_item = $cart_contents[1]; + + + $this->assertEquals( 1.67, edd_get_item_discount_amount( $first_item, $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 8.33, edd_get_item_discount_amount( $second_item, $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_amount_two_identical_items() { + + edd_add_to_cart( self::$downloads[0]->ID ); + edd_add_to_cart( self::$downloads[0]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = $cart_contents[0]; + $second_item = $cart_contents[1]; + + $this->assertEquals( 5, edd_get_item_discount_amount( $first_item, $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 5, edd_get_item_discount_amount( $second_item, $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_amount_percentage_discount() { + $percentage_discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + $percentage_discount = edd_get_discount( $percentage_discount_id ); + + edd_add_to_cart( self::$downloads[0]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = $cart_contents[0]; + + $this->assertEquals( 4.0, edd_get_item_discount_amount( $first_item, $cart_contents, array( $percentage_discount->code ) ) ); + } + + public function test_get_item_discount_amount_empty_item() { + + edd_add_to_cart( self::$downloads[0]->ID ); + + $cart_contents = edd_get_cart_contents(); + + $this->assertEquals( 0, edd_get_item_discount_amount( array(), $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_amount_no_discounts() { + + edd_add_to_cart( self::$downloads[0]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = $cart_contents[0]; + + $this->assertEquals( 0, edd_get_item_discount_amount( $first_item, $cart_contents, array() ) ); + } + + public function test_percentage_discount_product_requirements() { + $percentage_discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses( + array( + 'product_reqs' => array( self::$downloads[0]->ID ), + 'scope' => 'not_global', + ) + ); + $percentage_discount = edd_get_discount( $percentage_discount_id ); + + edd_add_to_cart( self::$downloads[0]->ID ); + edd_add_to_cart( self::$downloads[1]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = $cart_contents[0]; + $second_item = $cart_contents[1]; + + $this->assertEquals( 4.0, edd_get_item_discount_amount( $first_item, $cart_contents, array( $percentage_discount->code ) ) ); + $this->assertEquals( 0, edd_get_item_discount_amount( $second_item, $cart_contents, array( $percentage_discount->code ) ) ); + } + + public function test_get_item_discount_amount_single_item_discount_more_than_item() { + + $discount_id = Helpers\EDD_Helper_Discount::create_simple_flat_discount( + array( + 'amount' => 25, + ) + ); + $discount = edd_get_discount( $discount_id ); + + edd_add_to_cart( self::$downloads[0]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = reset( $cart_contents ); + + $this->assertEquals( 20, edd_get_item_discount_amount( $first_item, $cart_contents, array( $discount ) ) ); + } + + public function test_get_item_discount_amount_single_item_invalid_discount() { + edd_add_to_cart( self::$downloads[0]->ID ); + + $cart_contents = edd_get_cart_contents(); + $first_item = reset( $cart_contents ); + + $this->assertEquals( 0, edd_get_item_discount_amount( $first_item, $cart_contents, array( 'not_a_valid_code' ) ) ); + } + + public function test_get_item_discount_price_id_requirements() { + $variable_download = Helpers\EDD_Helper_Download::create_variable_download(); + + edd_update_discount( self::$discount_id, array( 'product_reqs' => array( $variable_download->ID . '_' . 0 ) ) ); + + edd_add_to_cart( $variable_download->ID, array( 'price_id' => array( 0, 1 ), ) ); + + $cart_contents = edd_get_cart_contents(); + $first_item = reset( $cart_contents ); + $second_item = end( $cart_contents ); + + $this->assertEquals( 10, edd_get_item_discount_amount( $first_item, $cart_contents, array( self::$discount->code ) ) ); + $this->assertEquals( 0, edd_get_item_discount_amount( $second_item, $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_price_id_requirements_all_variations() { + $variable_download = Helpers\EDD_Helper_Download::create_variable_download(); + + edd_update_discount( self::$discount_id, array( 'product_reqs' => array( $variable_download->ID ) ) ); + + edd_add_to_cart( $variable_download->ID, array( 'price_id' => 0 ) ); + + $cart_contents = edd_get_cart_contents(); + $first_item = reset( $cart_contents ); + + $this->assertEquals( 10, edd_get_item_discount_amount( $first_item, $cart_contents, array( self::$discount->code ) ) ); + } + + public function test_get_item_discount_amount_product_required_and_excluded() { + edd_update_discount( + self::$discount_id, + array( + 'excluded_products' => array( self::$downloads[1]->ID ), + 'product_reqs' => array( self::$downloads[1]->ID ), + ) + ); + + edd_add_to_cart( self::$downloads[1]->ID ); + + $cart_contents = edd_get_cart_contents(); + + $this->assertFalse( self::$discount->is_valid() ); + $this->assertEquals( 0, edd_get_item_discount_amount( $cart_contents[0], $cart_contents, array( self::$discount->code ) ) ); + } +} diff --git a/tests/discounts/tests-shortcodes.php b/tests/discounts/tests-shortcodes.php new file mode 100644 index 00000000000..2cc9417e5c2 --- /dev/null +++ b/tests/discounts/tests-shortcodes.php @@ -0,0 +1,48 @@ +truncate(); + } + + public function test_discounts_shortcode() { + Helpers\EDD_Helper_Discount::create_simple_percent_discount(); + + $actual = edd_discounts_shortcode( array() ); + + $this->assertIsString( $actual ); + $this->assertEquals( '
    • 20OFF - 20.00%
    ', $actual ); + } + + public function test_discounts_shortcode_no_active_discounts() { + $discount_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount_nodates_nouses(); + edd_update_discount_status( $discount_id, 'inactive' ); + + $actual = edd_discounts_shortcode( array() ); + + $this->assertIsString( $actual ); + $this->assertEquals( '
    • No discounts found
    ', $actual ); + } + +} diff --git a/tests/downloads/process/tests-complete.php b/tests/downloads/process/tests-complete.php new file mode 100644 index 00000000000..7c76630863d --- /dev/null +++ b/tests/downloads/process/tests-complete.php @@ -0,0 +1,181 @@ +assertTrue( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => self::$order->items[0]->product_id + ) ) ); + } + + public function test_order_with_item_variable_price_should_return_true() { + // Specifying price ID + $this->assertTrue( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => self::$variable_download->ID, + 'price_id' => 0, + ) ) ); + } + + public function test_order_with_item_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' => self::$order->items[0]->product_id, + 'price_id' => null, + ) ) ); + } + + /** + * If specifying a price ID that was not purchased in this order, files cannot be downloaded + * + */ + 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' => self::$variable_download->ID, + 'price_id' => 2, + ) ) ); + } + + public function test_bundled_download_should_be_deliverable() { + $this->assertTrue( + edd_order_grants_access_to_download_files( + array( + 'order_id' => self::$order->id, + 'product_id' => self::$bundled_download->ID, + ) + ) + ); + } + + /** + * Test that a completed order can download the file. + */ + public function test_order_can_download() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[0]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => false, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[0]->product_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertTrue( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a completed order can download the file with a price ID. + */ + public function test_order_can_download_with_price_id_condition_all() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertTrue( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a completed order cannot download the file with a different Price ID than purchased. + */ + public function test_order_cannot_download_different_price_id() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id + 1, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id + 1 ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a completed order can download the file with a price ID. + */ + public function test_order_can_download_with_purchased_bundle() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$bundled_download->get_bundled_downloads()[0], + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$bundled_download->get_bundled_downloads()[0] ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertTrue( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } +} diff --git a/tests/downloads/process/tests-misc.php b/tests/downloads/process/tests-misc.php new file mode 100644 index 00000000000..2b90a142ffb --- /dev/null +++ b/tests/downloads/process/tests-misc.php @@ -0,0 +1,156 @@ +assertEquals( $file, edd_set_requested_file_scheme( $https_file, array(), '' ) ); + } + + /** + * If no order exists with the provided ID, files cannot be downloaded + */ + 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 + ) ) ); + } + + /** + * If specifying a product ID that doesn't exist in the order, files cannot be downloaded + */ + 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' => 9999 + ) ) ); + } + + /** + * Test the file download token. + */ + 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 ) ); + } + + /** + * Test the file download toekn whe items are out of order. + */ + 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 ) ); + } + + /** + * Test custom parameters being including in the download 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 ) ); + } + + /** + * Test that a completed order but an expired link cannot download the file. + */ + public function test_order_cannot_download_expired_link() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[0]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) - HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => false, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[0]->product_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertTrue( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } +} diff --git a/tests/downloads/process/tests-on-hold.php b/tests/downloads/process/tests-on-hold.php new file mode 100644 index 00000000000..7af6496910a --- /dev/null +++ b/tests/downloads/process/tests-on-hold.php @@ -0,0 +1,157 @@ +id, 'on_hold' ); + } + + /** + * If an order has been fully refunded, files cannot be downloaded for any of the items + */ + public function test_pending_order_should_return_false() { + foreach ( self::$order->items as $item ) { + $order_item_args = array( + 'order_id' => self::$order->id, + 'product_id' => $item->product_id, + ); + + if ( ! is_null( $item->price_id ) ) { + $args['price_id'] = $item->price_id; + } + + $this->assertFalse( edd_order_grants_access_to_download_files( $order_item_args ) ); + } + } + + /** + * Test that an on hold order cannot download the file. + */ + public function test_order_cannot_download() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[0]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => false, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[0]->product_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that an on hold order cannot download the file with a price ID. + */ + public function test_order_cannot_download_with_price_id_condition_all() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that an on hold order cannot download the file with a different Price ID than purchased. + */ + public function test_order_cannot_download_different_price_id() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id + 1, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id + 1 ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that an on hold order cannot download the file with a price ID. + */ + public function test_order_cannot_download_with_purchased_bundle() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$bundled_download->get_bundled_downloads()[0], + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$bundled_download->get_bundled_downloads()[0] ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } +} diff --git a/tests/downloads/process/tests-partially-refunded.php b/tests/downloads/process/tests-partially-refunded.php new file mode 100644 index 00000000000..76c4aa17405 --- /dev/null +++ b/tests/downloads/process/tests-partially-refunded.php @@ -0,0 +1,115 @@ +id, + array( + array( + 'order_item_id' => self::$order->items[0]->id, + 'subtotal' => self::$order->items[0]->subtotal - self::$order->items[0]->discount, + 'tax' => self::$order->items[0]->tax, + ) + ) + ); + + // Fetch the order again, so we have the latest data. + self::$order = edd_get_order( self::$order->id ); + } + + /** + * If an order item has been refunded, the associated files can no longer be downloaded. + */ + public function test_refunded_item_in_partially_refunded_order_should_return_false() { + $this->assertFalse( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => self::$order->items[0]->product_id + ) ) ); + } + + /** + * If an order has been partially refunded, the item that's still `complete` can still be downloaded. + */ + 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' => self::$order->items[1]->product_id, + 'price_id' => self::$order->items[1]->price_id, + ) ) ); + } + + /** + * Test that a partially refunded order cannot download the file for the refunded item. + */ + public function test_order_cannot_download_refunded_item() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[0]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => false, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[0]->product_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a partially refunded order cannot download the file with a price ID. + */ + public function test_order_can_download_non_refunded_item() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertTrue( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } +} diff --git a/tests/downloads/process/tests-pending.php b/tests/downloads/process/tests-pending.php new file mode 100644 index 00000000000..75a27cee8a5 --- /dev/null +++ b/tests/downloads/process/tests-pending.php @@ -0,0 +1,157 @@ +id, 'pending' ); + } + + /** + * If an order has been fully refunded, files cannot be downloaded for any of the items + */ + public function test_pending_order_should_return_false() { + foreach ( self::$order->items as $item ) { + $order_item_args = array( + 'order_id' => self::$order->id, + 'product_id' => $item->product_id, + ); + + if ( ! is_null( $item->price_id ) ) { + $args['price_id'] = $item->price_id; + } + + $this->assertFalse( edd_order_grants_access_to_download_files( $order_item_args ) ); + } + } + + /** + * Test that a pending order cannot download the file. + */ + public function test_order_cannot_download() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[0]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => false, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[0]->product_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a pending order cannot download the file with a price ID. + */ + public function test_order_cannot_download_with_price_id_condition_all() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a pending order cannot download the file with a different Price ID than purchased. + */ + public function test_order_cannot_download_different_price_id() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id + 1, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id + 1 ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a pending order cannot download the file with a price ID. + */ + public function test_order_cannot_download_with_purchased_bundle() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$bundled_download->get_bundled_downloads()[0], + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$bundled_download->get_bundled_downloads()[0] ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } +} diff --git a/tests/downloads/process/tests-refunded.php b/tests/downloads/process/tests-refunded.php new file mode 100644 index 00000000000..d64c657346c --- /dev/null +++ b/tests/downloads/process/tests-refunded.php @@ -0,0 +1,165 @@ +id ); + self::$refunded_order = edd_get_order( $refund_id ); + + // Fetch the order again, so we have the latest data. + self::$order = edd_get_order( self::$order->id ); + } + + /** + * If an order has been fully refunded, files cannot be downloaded for any of the items. + */ + public function test_fully_refunded_order_should_return_false() { + foreach ( self::$order->items as $item ) { + $order_item_args = array( + 'order_id' => self::$order->id, + 'product_id' => $item->product_id, + ); + + if ( ! is_null( $item->price_id ) ) { + $args['price_id'] = $item->price_id; + } + + $this->assertFalse( edd_order_grants_access_to_download_files( $order_item_args ) ); + } + } + + /** + * Test that a refunded order cannot download the file. + */ + public function test_order_cannot_download() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[0]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => false, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[0]->product_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a refunded order cannot download the file with a price ID. + */ + public function test_order_cannot_download_with_price_id_condition_all() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a refunded order cannot download the file with a price ID. + */ + public function test_order_cannot_download_with_purchased_bundle() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$bundled_download->get_bundled_downloads()[0], + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$bundled_download->get_bundled_downloads()[0] ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + public function test_cannot_use_refund_order_to_download() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$refunded_order->items[0]->product_id, + 'email' => self::$refunded_order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => false, + 'key' => self::$refunded_order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$refunded_order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$refunded_order, self::$refunded_order->email, 0, self::$refunded_order->items[0]->product_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } +} diff --git a/tests/downloads/process/tests-revoked.php b/tests/downloads/process/tests-revoked.php new file mode 100644 index 00000000000..0b88e89ab96 --- /dev/null +++ b/tests/downloads/process/tests-revoked.php @@ -0,0 +1,157 @@ +id, 'revoked' ); + } + + /** + * If an order has been fully refunded, files cannot be downloaded for any of the items + */ + public function test_pending_order_should_return_false() { + foreach ( self::$order->items as $item ) { + $order_item_args = array( + 'order_id' => self::$order->id, + 'product_id' => $item->product_id, + ); + + if ( ! is_null( $item->price_id ) ) { + $args['price_id'] = $item->price_id; + } + + $this->assertFalse( edd_order_grants_access_to_download_files( $order_item_args ) ); + } + } + + /** + * Test that a pending order cannot download the file. + */ + public function test_order_cannot_download() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[0]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => false, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[0]->product_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a pending order cannot download the file with a price ID. + */ + public function test_order_cannot_download_with_price_id_condition_all() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a pending order cannot download the file with a different Price ID than purchased. + */ + public function test_order_cannot_download_different_price_id() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$order->items[1]->product_id, + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'price_id' => self::$order->items[1]->price_id + 1, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$order->items[1]->product_id, self::$order->items[1]->price_id + 1 ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } + + /** + * Test that a pending order cannot download the file with a price ID. + */ + public function test_order_cannot_download_with_purchased_bundle() { + // Add our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::add_download_files(); + + $args = array( + 'download' => self::$bundled_download->get_bundled_downloads()[0], + 'email' => self::$order->email, + 'expire' => current_time( 'timestamp' ) + HOUR_IN_SECONDS, + 'file_key' => 0, + 'key' => self::$order->payment_key, + 'eddfile' => sprintf( '%d:%d:%d', self::$order->id, 0, rawurlencode( '' ) ), + 'ttl' => '', + ); + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + $file_download_url = edd_get_download_file_url( self::$order, self::$order->email, 0, self::$bundled_download->get_bundled_downloads()[0] ); + $this->go_to( $file_download_url ); + $process_signed_url = edd_process_signed_download_url( $args ); + + $this->assertFalse( $process_signed_url['has_access'] ); + + // Remove our hook to 'fake' files to download. + Helpers\EDD_Helper_Download::remove_download_files(); + } +} 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-limit.php b/tests/downloads/tests-download-limit.php new file mode 100644 index 00000000000..5eeb60e4e90 --- /dev/null +++ b/tests/downloads/tests-download-limit.php @@ -0,0 +1,62 @@ +ID ); + + $download_no_limit = Helpers\EDD_Helper_Download::create_simple_download(); + delete_post_meta( $download_no_limit->ID, '_edd_download_limit' ); + self::$download_no_limit = edd_get_download( $download_no_limit->ID ); + } + + public function test_download_limit_is_20() { + $this->assertEquals( 20, self::$simple_download->file_download_limit ); + } + + public function test_get_download_limit_is_20() { + $this->assertEquals( 20, self::$simple_download->get_file_download_limit() ); + } + + public function test_get_file_download_limit() { + $this->assertEquals( 20, edd_get_file_download_limit( self::$simple_download->ID ) ); + } + + public function test_get_file_download_limit_override() { + $this->assertEquals( 1, edd_get_file_download_limit_override( self::$simple_download->ID, 1 ) ); + } + + public function test_is_file_at_download_limit() { + $this->assertFalse( edd_is_file_at_download_limit( self::$simple_download->ID, 1, 1 ) ); + } + + public function test_get_file_download_limit_setting_is_2() { + $this->assertEquals( 2, self::$download_no_limit->get_file_download_limit() ); + } + + public function test_get_file_download_limit_setting_is_0() { + edd_delete_option( 'file_download_limit' ); + $download = edd_get_download( self::$download_no_limit->ID ); + $this->assertEquals( 0, $download->get_file_download_limit() ); + } +} 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-download-search.php b/tests/downloads/tests-download-search.php new file mode 100644 index 00000000000..9a77c07daec --- /dev/null +++ b/tests/downloads/tests-download-search.php @@ -0,0 +1,99 @@ +post->create_many( + 5, + array( + 'post_type' => 'download', + ) + ); + } + + public static function tearDownAfterClass(): void { + EDD_Helper_Download::delete_all_downloads(); + + parent::tearDownAfterClass(); + } + + public function tearDown(): void { + parent::tearDown(); + unset( $_GET['s'] ); + } + + public function test_search_empty_string() { + $_GET['s'] = 'test'; + $search = new \EDD\Downloads\Search(); + $results = $search->search(); + + $this->assertEmpty( $results ); + } + + public function test_search() { + $_GET['s'] = 'Post title'; + $search = new \EDD\Downloads\Search(); + $results = $search->search(); + + $this->assertCount( 5, $results ); + } + + /** + * Search for a specific title. + * + * @return void + */ + public function test_search_specific_title() { + self::factory()->post->create( + array( + 'post_type' => 'download', + 'post_title' => 'Post title Specific', + ) + ); + self::factory()->post->create( + array( + 'post_type' => 'download', + 'post_title' => 'Post title Again Specific', + ) + ); + + $_GET['s'] = '"Post title Specific"'; + $search = new \EDD\Downloads\Search(); + $results = $search->search(); + + $this->assertCount( 1, $results ); + } + + /** + * Search for a fuzzy title. + * + * @return void + */ + public function test_search_fuzzy_title() { + self::factory()->post->create( + array( + 'post_type' => 'download', + 'post_title' => 'Post title Fuzzy', + ) + ); + self::factory()->post->create( + array( + 'post_type' => 'download', + 'post_title' => 'Post title Again Fuzzy', + ) + ); + + $_GET['s'] = 'Post title Fuzzy'; + $search = new \EDD\Downloads\Search(); + $results = $search->search(); + + $this->assertCount( 2, $results ); + } +} diff --git a/tests/downloads/tests-download-validation.php b/tests/downloads/tests-download-validation.php new file mode 100644 index 00000000000..988c19bfc09 --- /dev/null +++ b/tests/downloads/tests-download-validation.php @@ -0,0 +1,30 @@ +assertFalse( Process::validate( '' ) ); + } + + public function test_file_path_is_valid() { + $this->assertTrue( Process::validate( 'https://example.org/wp-content/uploads/edd/2019/05/test-file.zip' ) ); + } + + public function test_file_path_is_invalid() { + $this->assertFalse( Process::validate( 'https://example.org/wp-content/uploads/2024/07/../../../../../../../../../../../../etc/passwd' ) ); + } + + public function test_file_path_is_invalid_windows_absolute_path() { + $this->assertFalse( Process::validate( 'C:\Users\Public\Downloads\test-file.zip' ) ); + } + + public function test_file_path_is_valid_windows_absolute_path_in_dev() { + add_filter( 'edd_is_dev_environment', '__return_true' ); + $this->assertTrue( Process::validate( 'C:\Users\Public\Downloads\test-file.zip' ) ); + remove_filter( 'edd_is_dev_environment', '__return_true' ); + } +} diff --git a/tests/downloads/tests-downloads-service.php b/tests/downloads/tests-downloads-service.php new file mode 100644 index 00000000000..917e7167887 --- /dev/null +++ b/tests/downloads/tests-downloads-service.php @@ -0,0 +1,63 @@ +get_service(); + + $this->assertFalse( $download->is_service() ); + } + + public function test_download_is_service_with_term_empty_options_is_false() { + $download = $this->get_service(); + $category = wp_insert_term( 'Test Category', 'download_category' ); + $terms = wp_set_object_terms( $download->ID, array( $category['term_id'] ), 'download_category' ); + + $this->assertFalse( $download->is_service() ); + } + + public function test_download_is_service_by_type_is_true() { + $download = $this->get_service(); + update_post_meta( $download->ID, '_edd_product_type', 'service' ); + + $this->assertTrue( $download->is_service() ); + } + + public function test_download_is_service_by_type_bundle_is_false() { + $download = $this->get_service(); + update_post_meta( $download->ID, '_edd_product_type', 'bundle' ); + + $this->assertFalse( $download->is_service() ); + } + + public function test_download_is_service_with_term_is_true() { + $download = $this->get_service(); + $category = wp_insert_term( 'Test Category', 'download_category' ); + $terms = wp_set_object_terms( $download->ID, array( $category['term_id'] ), 'download_category' ); + edd_update_option( 'edd_das_service_categories', array( $category['term_id'] ) ); + + $this->assertTrue( $download->is_service() ); + } + + public function test_download_is_service_with_term_not_in_options_is_false() { + $download = $this->get_service(); + $category = wp_insert_term( 'Test Category', 'download_category' ); + $terms = wp_set_object_terms( $download->ID, array( $category['term_id'] ), 'download_category' ); + edd_update_option( 'edd_das_service_categories', array( 1234, 5678 ) ); + + $this->assertFalse( $download->is_service() ); + } + + private function get_service() { + $download = Helpers\EDD_Helper_Download::create_simple_download(); + $download_id = $download->ID; + delete_post_meta( $download_id, 'edd_download_files' ); + + return new \EDD\Downloads\Service( $download_id ); + } +} diff --git a/tests/downloads/tests-downloads.php b/tests/downloads/tests-downloads.php new file mode 100644 index 00000000000..139ea419185 --- /dev/null +++ b/tests/downloads/tests-downloads.php @@ -0,0 +1,341 @@ +_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, + 'index' => 0, + ), + array( + 'name' => 'Advanced', + 'amount' => 100, + 'index' => 1, + ) + ); + $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( 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->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_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->assertIsArray( edd_get_variable_prices( $download->ID ) ); + $this->assertCount( 0, edd_get_variable_prices( $download->ID ) ); + } + + public function test_download_has_variable_pricing_but_prices_as_empty_string() { + $download = Helpers\EDD_Helper_Download::create_simple_download(); + update_post_meta( $download->ID, '_variable_pricing', true ); + update_post_meta( $download->ID, 'edd_variable_prices', '' ); + + $this->assertTrue( edd_has_variable_prices( $download->ID ) ); + $this->assertIsArray( edd_get_variable_prices( $download->ID ) ); + $this->assertCount( 0, edd_get_variable_prices( $download->ID ) ); + } +} diff --git a/tests/emails/tests-actions.php b/tests/emails/tests-actions.php new file mode 100644 index 00000000000..75ed31786b5 --- /dev/null +++ b/tests/emails/tests-actions.php @@ -0,0 +1,277 @@ +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 ); + self::$order = edd_get_order( self::$payment_id ); + } + + /** + * 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 is a legacy filter that we need to test, simply for extensions that unhook it. + $this->assertarrayHasKey( 'edd_admin_email_notice', $wp_filter['edd_admin_sale_notice'][10] ); + + // Verify the order receipt email is hooked. + $hooked_into = array_keys( $wp_filter['edd_after_order_actions'][9999] ); + $has_hook = $this->determine_if_hook_found( 'send_order_emails', $hooked_into ); + $this->assertTrue( $has_hook, 'Did not find send_order_emails hook for edd_after_order_actions' ); + + // Verify the resend order receipt email is hooked. + $hooked_into = array_keys( $wp_filter['edd_email_links'][10] ); + $has_hook = $this->determine_if_hook_found( 'resend_order_receipt', $hooked_into ); + $this->assertTrue( $has_hook, 'Did not find resend_order_receipt hook for edd_email_links' ); + + // Verify the resend order receipt email is hooked. + $hooked_into = array_keys( $wp_filter['edd_send_test_email'][10] ); + $has_hook = $this->determine_if_hook_found( 'send_test_email', $hooked_into ); + $this->assertTrue( $has_hook, 'Did not find edd_send_test_email hook for send_test_email' ); + } + + public function test_admin_notice_emails() { + $expected = array( 'admin@example.org' ); + + $this->assertEquals( $expected, edd_get_admin_notice_emails() ); + } + + 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() { + $admin_order_notice = new \EDD\Emails\Templates\AdminOrderNotice(); + $email = $admin_order_notice->get_default( 'content' ); + + $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_edd_get_default_sale_notification_email_legacy() { + $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_get_from_name() { + $this->assertEquals( get_bloginfo( 'name' ), EDD()->emails->get_from_name() ); + } + + public function test_get_from_address() { + $this->assertEquals( get_bloginfo( 'admin_email' ), EDD()->emails->get_from_address() ); + } + + public function test_fallback_for_invalid_from_address() { + edd_update_option( 'from_email', 'not-an-email' ); + + $this->assertEquals( get_bloginfo( 'admin_email' ), EDD()->emails->get_from_address() ); + } + + public function test_get_content_type() { + $this->assertEquals( 'text/html', EDD()->emails->get_content_type() ); + + EDD()->emails->content_type = 'text/plain'; + + $this->assertEquals( 'text/plain', EDD()->emails->get_content_type() ); + + } + + public function test_get_headers() { + $from_name = EDD()->emails->get_from_name(); + $from_address = EDD()->emails->get_from_address(); + + $this->assertStringContainsString( "From: {$from_name} <{$from_address}>", EDD()->emails->get_headers() ); + } + + public function test_get_heading() { + EDD()->emails->__set( 'heading', 'Purchase Receipt' ); + + $this->assertEquals( 'Purchase Receipt', EDD()->emails->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 ); + } + + private function determine_if_hook_found( $desired_hook, $hooks ) { + $found = false; + + foreach ( $hooks as $hook ) { + if ( false !== strpos( $hook, $desired_hook ) ) { + $found = true; + } + } + + return $found; + } +} diff --git a/tests/emails/tests-admin-order-notice.php b/tests/emails/tests-admin-order-notice.php new file mode 100644 index 00000000000..3de14170e84 --- /dev/null +++ b/tests/emails/tests-admin-order-notice.php @@ -0,0 +1,118 @@ +get_email_by_id( self::$id ); + } + + public function test_email_is_enabled() { + $this->assertTrue( self::$email->status ); + $email = edd_get_email( self::$id ); + $this->assertTrue( $email->is_enabled() ); + } + + public function test_admin_notice_disabled() { + $this->assertFalse( edd_admin_notices_disabled() ); + } + + public function test_email_id_is_correct() { + $this->assertEquals( self::$id, self::$email->email_id ); + } + + public function test_email_name_is_correct() { + $this->assertEquals( 'Admin Sale Notification', self::$email->get_name() ); + } + + public function test_email_recipient_is_correct() { + $this->assertEquals( 'admin', self::$email->recipient ); + } + + public function test_email_context_is_correct() { + $this->assertEquals( 'order', self::$email->context ); + } + + public function test_email_subject() { + $this->assertEquals( 'New download purchase - Order #{payment_id}', self::$email->subject ); + } + + public function test_email_heading() { + $this->assertEquals( 'New Sale!', self::$email->heading ); + } + + public function test_email_body_matches_default() { + $this->assertEquals( self::$email->get_default( 'content' ), self::$email->content ); + } + + public function test_saving_email() { + $user_id = self::factory()->user->create( array( + 'role' => 'administrator', + ) ); + wp_set_current_user( $user_id ); + + $templates = new \EDD\Admin\Emails\Manager(); + $templates->save( + array( + 'edd_save_email_nonce' => wp_create_nonce( 'edd_save_email' ), + 'email_id' => self::$id, + 'subject' => 'New Subject', + 'heading' => 'New Heading', + 'content' => 'New Body', + 'status' => 0, + ) + ); + + $email = self::$registry->get_email_by_id( self::$id ); + + $this->assertEquals( 'New Subject', $email->subject ); + $this->assertEquals( 'New Heading', $email->heading ); + $this->assertEquals( 'New Body', $email->content ); + $this->assertFalse( $email->status ); + $this->assertTrue( edd_admin_notices_disabled() ); + } + + public function test_updating_email_meta_updates_recipient() { + edd_update_email_meta( self::$email->email->id, 'recipients', 'batman@thebatcave.co' ); + + $this->assertEquals( 'batman@thebatcave.co', self::$email->get_metadata( 'recipients' ) ); + } + + public function test_edit_url() { + $this->assertEquals( admin_url( 'edit.php?post_type=download&page=edd-emails&email=' . self::$id ), self::$email->get_edit_url() ); + } + + public function test_row_actions() { + $row_actions = self::$email->get_row_actions(); + + $this->assertArrayHasKey( 'edit', $row_actions ); + } +} diff --git a/tests/emails/tests-admin-order-refund.php b/tests/emails/tests-admin-order-refund.php new file mode 100644 index 00000000000..dc9b25a7391 --- /dev/null +++ b/tests/emails/tests-admin-order-refund.php @@ -0,0 +1,68 @@ +get_email_by_id( self::$id ); + } + + public function test_email_is_enabled() { + $this->assertFalse( self::$email->status ); + } + + public function test_email_id_is_correct() { + $this->assertEquals( self::$id, self::$email->email_id ); + } + + public function test_email_name_is_correct() { + $this->assertEquals( 'Admin Refund Notification', self::$email->get_name() ); + } + + public function test_email_recipient_is_correct() { + $this->assertEquals( 'admin', self::$email->recipient ); + } + + public function test_email_context_is_correct() { + $this->assertEquals( 'refund', self::$email->context ); + } + + public function test_email_subject() { + $this->assertEquals( 'An order has been refunded', self::$email->subject ); + } + + public function test_email_heading() { + $this->assertEmpty( self::$email->heading ); + } + + public function test_email_body_matches_default() { + $this->assertEquals( self::$email->get_default( 'content' ), self::$email->content ); + } +} diff --git a/tests/emails/tests-list-table.php b/tests/emails/tests-list-table.php new file mode 100644 index 00000000000..36ae835ab50 --- /dev/null +++ b/tests/emails/tests-list-table.php @@ -0,0 +1,70 @@ + 'email_template', + 'plural' => 'email_templates', + 'ajax' => false, + ) + ); + self::$list_table->prepare_items(); + } + + public function test_columns_has_name() { + $this->assertArrayHasKey( 'name', self::$list_table->get_columns() ); + $this->assertEquals( 'Email', self::$list_table->get_columns()['name'] ); + } + + public function test_columns_has_recipient() { + $this->assertArrayHasKey( 'recipient', self::$list_table->get_columns() ); + $this->assertEquals( 'Recipient', self::$list_table->get_columns()['recipient'] ); + } + + public function test_columns_has_sender() { + $this->assertArrayHasKey( 'sender', self::$list_table->get_columns() ); + $this->assertEquals( 'Sender', self::$list_table->get_columns()['sender'] ); + } + + public function test_columns_has_context() { + $this->assertArrayHasKey( 'context', self::$list_table->get_columns() ); + $this->assertEquals( 'Context', self::$list_table->get_columns()['context'] ); + } + + public function test_columns_has_subject() { + $this->assertArrayHasKey( 'subject', self::$list_table->get_columns() ); + $this->assertEquals( 'Subject', self::$list_table->get_columns()['subject'] ); + } + + public function test_columns_has_status() { + $this->assertArrayHasKey( 'status', self::$list_table->get_columns() ); + $this->assertEquals( 'Status', self::$list_table->get_columns()['status'] ); + } + + public function test_first_item_is_order_receipt() { + $this->assertEquals( 'order_receipt', self::$list_table->items[0]->email_id ); + } + + public function test_single_row_has_data() { + ob_start(); + self::$list_table->single_row( self::$list_table->items[0] ); + + $row = ob_get_clean(); + + $this->assertStringContainsString( 'Purchase Receipt', $row ); + $this->assertStringContainsString( 'Customer', $row ); + $this->assertStringContainsString( 'EDD Core', $row ); + $this->assertStringContainsString( 'Order', $row ); + $this->assertStringContainsString( 'data-id="order_receipt"', $row ); + } +} diff --git a/tests/emails/tests-manager.php b/tests/emails/tests-manager.php new file mode 100644 index 00000000000..7b9d5a5e1ca --- /dev/null +++ b/tests/emails/tests-manager.php @@ -0,0 +1,82 @@ +assertIsArray( $this->get_registry()->get_emails() ); + } + + public function test_get_emails_is_not_empty() { + $this->assertNotEmpty( $this->get_registry()->get_emails() ); + } + + public function test_order_receipt_template_exists() { + $this->assertTrue( array_key_exists( 'order_receipt', $this->get_registry()->get_emails() ) ); + } + + public function test_get_recipients_is_array() { + $this->assertIsArray( $this->get_registry()->get_recipients() ); + } + + public function test_get_recipients_is_not_empty() { + $this->assertNotEmpty( $this->get_registry()->get_recipients() ); + } + + public function test_get_recipients_is_correct() { + $this->assertEquals( + array( + 'customer' => __( 'Customer', 'easy-digital-downloads' ), + 'admin' => __( 'Admin', 'easy-digital-downloads' ), + 'user' => __( 'User', 'easy-digital-downloads' ), + ), + $this->get_registry()->get_recipients() + ); + } + + public function test_get_senders_is_array() { + $this->assertIsArray( $this->get_registry()->get_senders() ); + } + + public function test_get_senders_is_not_empty() { + $this->assertNotEmpty( $this->get_registry()->get_senders() ); + } + + public function test_get_senders_includes_edd() { + $this->assertTrue( array_key_exists( 'edd', $this->get_registry()->get_senders() ) ); + } + + public function test_new_id_generator_returns_string() { + $new_id = \EDD\Admin\Emails\Manager::get_new_id( 'license', 'license_new' ); + + $this->assertIsString( $new_id ); + $this->assertStringContainsString( 'license_', $new_id ); + } + + public function test_new_id_generator_returns_string_32() { + $new_id = \EDD\Admin\Emails\Manager::get_new_id( 'superduperlongstringprefixthatwillnotfitindatabase', 'license_new' ); + + $this->assertIsString( $new_id ); + $this->assertEquals( 32, strlen( $new_id ) ); + } + + public function test_new_id_generator_invalid_characters_returns_string() { + $new_id = \EDD\Admin\Emails\Manager::get_new_id( 'license_new!@#$%^&*()_+', 'license_new' ); + + $this->assertIsString( $new_id ); + $this->assertStringNotContainsString( '!', $new_id ); + } + + private static function get_registry() { + if ( ! self::$registry ) { + self::$registry = edd_get_email_registry(); + } + + return self::$registry; + } +} diff --git a/tests/emails/tests-new-user-admin.php b/tests/emails/tests-new-user-admin.php new file mode 100644 index 00000000000..abf72cd2e30 --- /dev/null +++ b/tests/emails/tests-new-user-admin.php @@ -0,0 +1,70 @@ +get_email_by_id( self::$id ); + } + + public function test_email_is_enabled() { + $this->assertTrue( self::$email->status ); + $email = edd_get_email( self::$id ); + $this->assertTrue( $email->is_enabled() ); + } + + public function test_email_id_is_correct() { + $this->assertEquals( self::$id, self::$email->email_id ); + } + + public function test_email_name_is_correct() { + $this->assertEquals( 'Admin New User Notification', self::$email->get_name() ); + } + + public function test_email_recipient_is_correct() { + $this->assertEquals( 'admin', self::$email->recipient ); + } + + public function test_email_context_is_correct() { + $this->assertEquals( 'user', self::$email->context ); + } + + public function test_email_subject() { + $this->assertEquals( '[{sitename}] New User Registration', self::$email->subject ); + } + + public function test_email_heading() { + $this->assertEquals( 'New user registration', self::$email->heading ); + } + + public function test_email_body_matches_default() { + $this->assertEquals( self::$email->get_default( 'content' ), self::$email->content ); + } +} diff --git a/tests/emails/tests-new-user.php b/tests/emails/tests-new-user.php new file mode 100644 index 00000000000..e2c523c75ff --- /dev/null +++ b/tests/emails/tests-new-user.php @@ -0,0 +1,70 @@ +get_email_by_id( self::$id ); + } + + public function test_email_is_enabled() { + $this->assertTrue( self::$email->status ); + $email = edd_get_email( self::$id ); + $this->assertTrue( $email->is_enabled() ); + } + + public function test_email_id_is_correct() { + $this->assertEquals( self::$id, self::$email->email_id ); + } + + public function test_email_name_is_correct() { + $this->assertEquals( 'New User Registration', self::$email->get_name() ); + } + + public function test_email_recipient_is_correct() { + $this->assertEquals( 'customer', self::$email->recipient ); + } + + public function test_email_context_is_correct() { + $this->assertEquals( 'user', self::$email->context ); + } + + public function test_email_subject() { + $this->assertEquals( '[{sitename}] Your username and password', self::$email->subject ); + } + + public function test_email_heading() { + $this->assertEquals( 'Your account info', self::$email->heading ); + } + + public function test_email_body_matches_default() { + $this->assertEquals( self::$email->get_default( 'content' ), self::$email->content ); + } +} diff --git a/tests/emails/tests-order-receipt.php b/tests/emails/tests-order-receipt.php new file mode 100644 index 00000000000..42999dcdb76 --- /dev/null +++ b/tests/emails/tests-order-receipt.php @@ -0,0 +1,114 @@ +get_email_by_id( self::$id ); + } + + public function test_email_is_enabled() { + $this->assertTrue( self::$email->status ); + $email = edd_get_email( self::$id ); + $this->assertTrue( $email->is_enabled() ); + } + + public function test_email_id_is_correct() { + $this->assertEquals( self::$id, self::$email->email_id ); + } + + public function test_email_name_is_correct() { + $this->assertEquals( 'Purchase Receipt', self::$email->get_name() ); + } + + public function test_email_recipient_is_correct() { + $this->assertEquals( 'customer', self::$email->recipient ); + } + + public function test_email_context_is_correct() { + $this->assertEquals( 'order', self::$email->context ); + } + + public function test_email_subject() { + $this->assertEquals( 'Purchase Receipt', self::$email->subject ); + } + + public function test_email_heading() { + $this->assertEquals( 'Purchase Receipt', self::$email->heading ); + } + + public function test_email_body_matches_default() { + $this->assertEquals( self::$email->get_default( 'content' ), self::$email->content ); + } + + public function test_saving_email() { + $user_id = self::factory()->user->create( array( + 'role' => 'administrator', + ) ); + wp_set_current_user( $user_id ); + + $manager = new \EDD\Admin\Emails\Manager(); + $manager->save( + array( + 'edd_save_email_nonce' => wp_create_nonce( 'edd_save_email' ), + 'email_id' => self::$id, + 'subject' => 'New Subject', + 'heading' => 'New Heading', + 'content' => 'New Body', + 'status' => 0, + ) + ); + + $email = self::$registry->get_email_by_id( self::$id ); + + $this->assertEquals( 'New Subject', $email->subject ); + $this->assertEquals( 'New Heading', $email->heading ); + $this->assertEquals( 'New Body', $email->content ); + $this->assertFalse( $email->status ); + } + + public function test_order_receipt_preview_email() { + $order_id = \EDD\Tests\Helpers\EDD_Helper_Payment::create_simple_payment(); + $email = new \EDD\Emails\Types\OrderReceipt( edd_get_order( $order_id ) ); + $email->is_preview = true; + $preview = $email->get_preview(); + + $this->assertStringContainsString( 'Please click on the link(s) below to download your files.', $preview ); + } + + /** + * @expectedDeprecated edd_get_option( 'purchase_receipt' ) + * + * @return void + */ + public function test_legacy_option_handling() { + $this->assertEquals( self::$email->content, edd_get_option( 'purchase_receipt' ) ); + } +} diff --git a/tests/emails/tests-order-refund.php b/tests/emails/tests-order-refund.php new file mode 100644 index 00000000000..2bfea7b3917 --- /dev/null +++ b/tests/emails/tests-order-refund.php @@ -0,0 +1,68 @@ +get_email_by_id( self::$id ); + } + + public function test_email_is_enabled() { + $this->assertFalse( self::$email->status ); + } + + public function test_email_id_is_correct() { + $this->assertEquals( self::$id, self::$email->email_id ); + } + + public function test_email_name_is_correct() { + $this->assertEquals( 'Refund Issued', self::$email->get_name() ); + } + + public function test_email_recipient_is_correct() { + $this->assertEquals( 'customer', self::$email->recipient ); + } + + public function test_email_context_is_correct() { + $this->assertEquals( 'refund', self::$email->context ); + } + + public function test_email_subject() { + $this->assertEquals( 'Your order has been refunded', self::$email->subject ); + } + + public function test_email_heading() { + $this->assertEmpty( self::$email->heading ); + } + + public function test_email_body_matches_default() { + $this->assertEquals( self::$email->get_default( 'content' ), self::$email->content ); + } +} diff --git a/tests/emails/tests-password-reset.php b/tests/emails/tests-password-reset.php new file mode 100644 index 00000000000..27a07f12b9a --- /dev/null +++ b/tests/emails/tests-password-reset.php @@ -0,0 +1,94 @@ + 'Login Page', + 'post_status' => 'publish', + 'post_type' => 'page', + 'post_content' => '', + ) + ); + edd_update_option( 'login_page', $login_page_id ); + + self::$registry = new \EDD\Emails\Templates\Registry(); + self::$email = self::$registry->get_email_by_id( self::$id ); + } + + public function test_email_is_enabled() { + $this->assertTrue( self::$email->status ); + } + + public function test_email_id_is_correct() { + $this->assertEquals( self::$id, self::$email->email_id ); + } + + public function test_email_name_is_correct() { + $this->assertEquals( 'Password Reset', self::$email->get_name() ); + } + + public function test_email_recipient_is_correct() { + $this->assertEquals( 'user', self::$email->recipient ); + } + + public function test_email_context_is_correct() { + $this->assertEquals( 'user', self::$email->context ); + } + + public function test_email_subject() { + $this->assertEquals( self::$email->get_default( 'subject' ), self::$email->subject ); + } + + public function test_email_heading() { + $this->assertEmpty( self::$email->heading ); + } + + public function test_email_body_matches_default() { + $this->assertEquals( self::$email->get_default( 'content' ), self::$email->content ); + } + + public function test_email_sender_is_wp() { + $this->assertEquals( 'wp', self::$email->sender ); + } + + public function test_email_can_edit_subject_is_false() { + $this->assertFalse( self::$email->can_edit( 'subject' ) ); + } + + public function test_email_can_edit_status_is_false() { + $this->assertFalse( self::$email->can_edit( 'status' ) ); + } + + public function test_email_has_required_tag() { + $this->assertEquals( 'password_reset_link', self::$email->required_tag ); + } +} diff --git a/tests/emails/tests-registry.php b/tests/emails/tests-registry.php new file mode 100644 index 00000000000..7486cd3e6fb --- /dev/null +++ b/tests/emails/tests-registry.php @@ -0,0 +1,200 @@ +assertTrue( class_exists( 'EDD\Emails\Registry' ) ); + } + + /** Test the is_registerd method */ + /** + * Test that the order_receipt email is properly registered. + */ + public function test_order_receipt_email_is_registered() { + $this->assertTrue( \EDD\Emails\Registry::is_registered( 'order_receipt' ) ); + } + + /** + * Test that the admin_order_notice email is properly registered. + */ + public function test_admin_order_notice_email_is_registered() { + $this->assertTrue( \EDD\Emails\Registry::is_registered( 'admin_order_notice' ) ); + } + + /** + * Test that using is_registered on an unregistered ID returns false. + */ + public function test_is_registered_returns_false_for_unregistered_email() { + $this->assertFalse( \EDD\Emails\Registry::is_registered( 'unregistered_email' ) ); + } + + /** Test the register method */ + + /** + * Test that we can register a valid email class. + */ + public function test_register_valid_email() { + \EDD\Emails\Registry::register( 'fake_email', 'EDD\Tests\Emails\ValidFakeEmail' ); + $this->assertTrue( \EDD\Emails\Registry::is_registered( 'fake_email' ) ); + + $fake_email = new \EDD\Tests\Emails\ValidFakeEmail(); + $this->assertInstanceOf( '\EDD\Emails\Email', $fake_email->email ); + $this->assertEquals( 'fake_email', $fake_email->email->email_id ); + $this->assertEmpty( $fake_email->email->is_enabled() ); + } + + /** + * Test that trying to register a duplicate ID is handeled properly. + * @expectException WPDieException + */ + public function test_register_duplicate_id_throws_exception() { + $this->setExpectedException( 'WPDieException', 'The email ID provided is already registered.' ); + \EDD\Emails\Registry::register( 'order_receipt', 'EDD\Emails\Types\OrderReceipt' ); + } + + /** + * Test that trying to register an email with no ID is handled properly. + * @expectException WPDieException + */ + public function test_register_no_id_throws_exception() { + $this->setExpectedException( 'WPDieException', 'An email ID and class must be provided.' ); + \EDD\Emails\Registry::register( '', 'EDD\Emails\Types\OrderReceipt' ); + } + + /** + * Test that trying to register an email with no class is handled properly. + * @expectException WPDieException + */ + public function test_register_no_class_throws_exception() { + $this->setExpectedException( 'WPDieException', 'An email ID and class must be provided.' ); + \EDD\Emails\Registry::register( 'missing_class', '' ); + } + + /** + * Test that trying to register an email with a class that does not exist is handled properly. + * @expectException WPDieException + */ + public function test_register_non_existent_class_throws_exception() { + $this->setExpectedException( 'WPDieException', 'The email class must exist and extend the EDD\Emails\Types\Email class.' ); + \EDD\Emails\Registry::register( 'non_existent_class', 'EDD\Tests\Emails\NonExistentClass' ); + } + + /** + * Test that trying to register an email with a class that does not extend the EDD\Emails\Types\Email class is handled properly. + * @expectException WPDieException + */ + public function test_register_non_email_class_throws_exception() { + $this->setExpectedException( 'WPDieException', 'The email class must exist and extend the EDD\Emails\Types\Email class.' ); + \EDD\Emails\Registry::register( 'non_email_class', 'EDD\Tests\Emails\InvalidFakeEmail' ); + } + + /** Test the get method */ + + /** + * Test that we can get a registered email class that has arguments in __construct(). + */ + public function test_get_registered_email_with_arguments() { + $email = \EDD\Emails\Registry::get( 'order_receipt', array( false ) ); + $this->assertInstanceOf( 'EDD\Emails\Types\OrderReceipt', $email ); + } + + public function test_order_receipt_magic_getter_gets_order() { + $order_id = EDD_Helper_Payment::create_simple_payment(); + $email = \EDD\Emails\Registry::get( 'order_receipt', array( edd_get_order( $order_id ) ) ); + + $this->assertInstanceOf( 'EDD\Orders\Order', $email->order ); + $this->assertEquals( $order_id, $email->order->id ); + } + + /** + * Test that we can get a registered email class that has no arguments in __construct(). + */ + public function test_get_registered_email_without_arguments() { + $email = \EDD\Emails\Registry::get( 'fake_email' ); + $this->assertInstanceOf( 'EDD\Tests\Emails\ValidFakeEmail', $email ); + } + + /** + * Test that getting a class for an unregistered email is handled properly. + * @expectException WPDieException + */ + public function test_get_unregistered_email_throws_exception() { + $this->setExpectedException( 'WPDieException', 'The email ID provided is not registered.' ); + $email = \EDD\Emails\Registry::get( 'unregistered_email' ); + } + + /** + * Test that getting a registered email class without the proper number of arguments throws an exception. + * @expectException WPDieException + */ + public function test_get_registered_email_with_incorrect_number_of_arguments_throws_exception() { + $this->setExpectedException( 'WPDieException', 'The number of arguments provided (0) does not match the number of arguments required (1) for EDD\Emails\Types\OrderReceipt.' ); + $email = \EDD\Emails\Registry::get( 'order_receipt' ); + } +} + +/** + * Setup a fake class that extends the EDD\Emails\Types\Email class. + */ +class ValidFakeEmail extends \EDD\Emails\Types\Email { + protected $id = 'fake_email'; + protected $context = 'unit_test'; + protected $recipient_type = 'fake'; + + protected function set_email_body_content() { + return 'I\'m {secret_identity}.'; + } + + protected function set_from_name() { + return 'Bruce Wayne'; + } + + protected function set_from_email() { + return 'bruce@waynefoundation.org'; + } + + protected function set_to_email() { + return 'jgordon@gothampolice.gov'; + } + + protected function set_headers() { + return array( + 'X-Batman' => 'I\'m Batman.', + ); + } + + protected function set_subject() { + return 'Important information'; + } + + protected function set_heading() { + return 'Important information'; + } + + protected function set_message() { + return 'I\'m Batman.'; + } + + protected function set_attachments() { + return array( + 'batmobile.jpg', + ); + } + + public function get_default_body_content() { + return ''; + } +} + +/** Create a class that does not extend the \EDD\Emails\Types\Email class */ +class InvalidFakeEmail {} diff --git a/tests/emails/tests-tags.php b/tests/emails/tests-tags.php new file mode 100644 index 00000000000..24b5e5a784c --- /dev/null +++ b/tests/emails/tests-tags.php @@ -0,0 +1,319 @@ + self::$payment_id, + 'object_type' => 'order', + 'transaction_id' => 'sample_transaction_id', + 'gateway' => 'manual', + 'status' => 'complete', + 'total' => self::$order->total, + 'date_created' => self::$order->date_created, + ) + ); + } + + public function test_email_tags_get_tags() { + $tags = edd_get_email_tags(); + + $this->assertIsArray( $tags ); + $this->assertTrue( edd_email_tag_exists( 'download_list' ) ); + $this->assertTrue( edd_email_tag_exists( 'file_urls' ) ); + $this->assertTrue( edd_email_tag_exists( 'name' ) ); + $this->assertTrue( edd_email_tag_exists( 'fullname' ) ); + $this->assertTrue( edd_email_tag_exists( 'username' ) ); + $this->assertTrue( edd_email_tag_exists( 'user_email' ) ); + $this->assertTrue( edd_email_tag_exists( 'date' ) ); + $this->assertTrue( edd_email_tag_exists( 'subtotal' ) ); + $this->assertTrue( edd_email_tag_exists( 'tax' ) ); + $this->assertTrue( edd_email_tag_exists( 'price' ) ); + $this->assertTrue( edd_email_tag_exists( 'payment_id' ) ); + $this->assertTrue( edd_email_tag_exists( 'payment_method' ) ); + $this->assertTrue( edd_email_tag_exists( 'sitename' ) ); + $this->assertTrue( edd_email_tag_exists( 'receipt_link' ) ); + $this->assertTrue( edd_email_tag_exists( 'login_link' ) ); + $this->assertTrue( edd_email_tag_exists( 'transaction_id' ) ); + $this->assertTrue( edd_email_tag_exists( 'refund_link' ) ); + $this->assertTrue( edd_email_tag_exists( 'refund_amount' ) ); + $this->assertTrue( edd_email_tag_exists( 'refund_id' ) ); + } + + 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( '
    _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..a7a1d46d106 --- /dev/null +++ b/tests/gateways/tests-paypal.php @@ -0,0 +1,680 @@ +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" +}'; + } + + 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/Licenses.php b/tests/helpers/Licenses.php new file mode 100644 index 00000000000..6334d2bf649 --- /dev/null +++ b/tests/helpers/Licenses.php @@ -0,0 +1,89 @@ + 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, + ) + ); + + return (object) $license_data; + } + + public static function get_pro_license( $args = array() ) { + $license_key = 'daksjfg98q3kjhJ3K4Q2354'; + update_site_option( 'edd_pro_license_key', $license_key ); + + $pass_handler = new \EDD\Admin\PassHandler\Handler(); + $pass_handler->update_pro_license( self::get_pass_license_data( $args ) ); + + return $pass_handler->get_pro_license(); + } + + public static function delete_pro_license() { + delete_site_option( 'edd_pro_license_key' ); + delete_site_option( 'edd_pro_license' ); + } + + public static function get_stripe_license_data( $args = array() ) { + $license_data = wp_parse_args( + $args, + 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, + ) + ); + + return (object) $license_data; + } + + public static function get_stripe_license( $args = array() ) { + $product_name = 'Stripe Pro Payment Gateway'; + $license_key = 'bgvear89p7ty4qbrjkc4'; + + edd_update_option( self::get_stripe_option_name(), $license_key ); + $license = new \EDD\Licensing\License( $product_name ); + $license->save( self::get_stripe_license_data( $args ) ); + + return $license->get(); + } + + public static function delete_stripe_license() { + edd_delete_option( self::get_stripe_option_name() ); + delete_option( 'edd_stripe_pro_payment_gateway_license_active' ); + } + + private static function get_stripe_option_name() { + $product_name = 'Stripe Pro Payment Gateway'; + $shortname = 'edd_' . preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $product_name ) ) ); + + return "{$shortname}_license_key"; + } +} diff --git a/tests/helpers/class-helper-discount.php b/tests/helpers/class-helper-discount.php new file mode 100644 index 00000000000..fcc76bffcfb --- /dev/null +++ b/tests/helpers/class-helper-discount.php @@ -0,0 +1,184 @@ + '20 Percent Off', + 'code' => '20OFF', + 'status' => 'active', + 'type' => 'percent', + 'amount' => '20', + 'use_count' => 0, + 'max_uses' => 10, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start_date' => date( 'm/d/Y', time() ) . ' 00:00:00', + 'end_date' => date( 'm/d/Y', time() ) . ' 23:59:59', + ) + ) + ); + } + + /** + * Create a simple percentage discount. + * + * @since 2.3 + */ + public static function create_simple_percent_discount_nodates_nouses( $args = array() ) { + return edd_add_discount( + wp_parse_args( + $args, + array( + 'name' => '20 Percent Off NO DATES NO USES', + 'code' => '20OFF', + 'status' => 'active', + 'type' => 'percent', + 'amount' => '20', + 'use_count' => 0, + 'max_uses' => 0, + 'min_charge_amount' => 0, + 'product_condition' => 'all', + ) + ) + ); + } + + /** + * 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( $args = array() ) { + return edd_add_discount( + wp_parse_args( + $args, + 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' => date( 'm/d/Y', time() ) . ' 00:00:00', + 'end_date' => 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..d41596dc785 --- /dev/null +++ b/tests/helpers/class-helper-download.php @@ -0,0 +1,351 @@ + 'download', + 'posts_per_page' => -1, + ) + ); + + foreach ( $query->posts as $post ) { + wp_delete_post( $post->ID, true ); + } + } + + /** + * Create a simple download. + * + * @since 2.3 + */ + public static function create_simple_download() { + + $post_id = wp_insert_post( array( + 'post_title' => '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 ); + } + + /** + * Add a filter for the edd_download_files hook. + * + * This allows us to 'fake' the download files for a download. + */ + public static function add_download_files() { + add_filter( 'edd_download_files', array( __CLASS__, 'helper_filter_download_files' ), 10, 3 ); + } + + /** + * Remove the filter for the edd_download_files hook. + */ + public static function remove_download_files() { + remove_filter( 'edd_download_files', array( __CLASS__, 'helper_filter_download_files' ), 10, 3 ); + } + + /** + * Filter the download files for a download, by adding fake files, for testing. + * + * @param array $files The download files. + * @param int $download_id The download ID. + * @param int $payment_id The payment ID. + * + * @return array The filtered download files. + */ + public function helper_filter_download_files( $files, $download_id, $payment_id ) { + $files = array( + array ( + 'index' => '0', + 'attachment_id' => '0', + 'thumbnail_size' => '', + 'name' => 'Test File', + 'file' => 'https://example.org/wp-content/uploads/edd/2019/05/test-file.zip', + 'condition' => 'all', + ), + array ( + 'index' => '1', + 'attachment_id' => '0', + 'thumbnail_size' => '', + 'name' => 'Test File', + 'file' => 'https://example.org/wp-content/uploads/edd/2019/05/test-file.zip', + 'condition' => '0', + ), + ); + + return $files; + } +} 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/class-process-download.php b/tests/helpers/class-process-download.php new file mode 100644 index 00000000000..08f12da63f7 --- /dev/null +++ b/tests/helpers/class-process-download.php @@ -0,0 +1,152 @@ +customer->create( array( 'user_id' => get_current_user_id() ) ); + self::$customer = new \EDD_Customer( self::$customer_id ); + + // Create a variable priced download. + $variable_download = EDD_Helper_Download::create_variable_download(); + self::$variable_download = new \EDD_Download( $variable_download->ID ); + + // Create a bundled download. + $bundled_download = EDD_Helper_Download::create_bundled_download(); + self::$bundled_download = new \EDD_Download( $bundled_download->ID ); + + self::$order = parent::edd()->order->create_and_get( + array( + 'customer_id' => self::$customer_id, + 'user_id' => get_current_user_id(), + 'email' => self::$customer->email, + 'status' => 'complete', + ) + ); + + // Add the variable priced download to the order. + edd_add_order_item( + array( + 'order_id' => self::$order->id, + 'product_id' => self::$variable_download->ID, + 'price_id' => 0, + 'product_name' => self::$variable_download->post_title, + 'status' => self::$order->status, + 'amount' => 100, + 'subtotal' => 100, + 'discount' => 5, + 'tax' => 25, + 'total' => 120, + 'quantity' => 1, + ) + ); + + // Add the bundled download to the order. + edd_add_order_item( + array( + 'order_id' => self::$order->id, + 'product_id' => self::$bundled_download->ID, + 'product_name' => self::$bundled_download->post_title, + 'status' => self::$order->status, + 'amount' => 100, + 'subtotal' => 100, + 'discount' => 5, + 'tax' => 25, + 'total' => 120, + 'quantity' => 1, + ) + ); + + // Since we've added items to the order, we need to update the amounts, so we can process refunds. + $amount = 0; + $subtotal = 0; + $discount = 0; + $tax = 0; + $total = 0; + + foreach ( self::$order->items as $item ) { + $amount += $item->amount; + $subtotal += $item->subtotal; + $discount += $item->discount; + $tax += $item->tax; + $total += $item->total; + } + + edd_update_order( + self::$order->id, + array( + 'amount' => $amount, + 'subtotal' => $subtotal, + 'discount' => $discount, + 'tax' => $tax, + 'total' => $total, + ) + ); + + // Since we've changed the order, we need to refresh the order object. + self::$order = edd_get_order( self::$order->id ); + } + + /** + * Delete the fixtures when done. + */ + public static function tearDownAfterClass(): void { + parent::tearDownAfterClass(); + + // Delete the customer. + parent::edd()->customer->delete( self::$customer_id ); + + // Delete the variable priced download. + EDD_Helper_Download::delete_download( self::$variable_download->ID ); + + // Delete the bundled download. + EDD_Helper_Download::delete_download( self::$bundled_download->ID ); + + // Delete the order. + parent::edd()->order->delete( self::$order->id ); + } +} 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 @@ +assertInstanceOf( '\\EDD\\HTML\\Elements', EDD()->html ); + } + + public function test_legacy_class_alias() { + $legacy_class = new \EDD_HTML_Elements(); + + $this->assertInstanceOf( '\\EDD\\HTML\\Elements', $legacy_class ); + } + + /** + * @covers EDD_HTML_Elements::select + */ + public function test_select_is_required() { + $select = EDD()->html->select( + array( + 'required' => true, + 'options' => array( + 1 => '1', + 2 => '2', + 3 => '3', + ), + ) + ); + + $this->assertStringContainsString( 'required', $select ); + } + + /** + * @covers EDD_HTML_Elements::select + */ + public function test_select_is_not_required() { + $select = EDD()->html->select( + array( + 'options' => array( + 1 => '1', + 2 => '2', + 3 => '3', + ) + ) + ); + + $this->assertStringNotContainsString( 'required', $select ); + } + + /** + * @covers EDD_HTML_Elements::text + */ + public function test_text_is_required() { + $this->assertStringContainsString( 'required', EDD()->html->text( array( 'required' => true ) ) ); + } + + /** + * @covers EDD_HTML_Elements::text + */ + public function test_text_is_not_required() { + $this->assertStringNotContainsString( 'required', EDD()->html->text() ); + } + + public function test_checkbox() { + $checkbox = EDD()->html->checkbox( + array( + 'name' => 'edd-checkbox', + 'label' => 'Checkbox', + ) + ); + + $this->assertStringContainsString( 'name="edd-checkbox"', $checkbox ); + $this->assertStringContainsString( '', $checkbox ); + } + + public function test_textarea() { + $textarea = EDD()->html->textarea( + array( + 'name' => 'edd-textarea', + 'label' => 'Textarea', + ) + ); + + $this->assertStringContainsString( 'name="edd-textarea"', $textarea ); + $this->assertStringContainsString( 'Fix the Checkout Page', + 'test' => 'edd_missing_purchase_page', + ); + + $result = self::$test->get_test_missing_purchase_page(); + + $this->assertSame( $expected, $result ); + + edd_update_option( 'purchase_page', $purchase_page ); + } + + /** + * @covers \EDD\Admin\SiteHealth\Direct::get_test_missing_purchase_page() + */ + public function test_get_test_missing_purchase_page_using_shortcode() { + $purchase_page = edd_get_option( 'purchase_page', false ); + $button_url = admin_url( 'post.php?post=' . $purchase_page . '&action=edit' ); + + // Get the purchase page content, so we can reset it later. + $purchase_page_content = get_post( $purchase_page )->post_content; + + // Update the purchase page content to use the legacy shortcode. + wp_update_post( + array( + 'ID' => $purchase_page, + 'post_content' => '[download_checkout]', + ) + ); + + $expected = array( + 'label' => 'Your checkout page is using the legacy shortcode', + 'status' => 'recommended', + 'badge' => array( + 'label' => 'Easy Digital Downloads', + 'color' => 'orange', + ), + 'description' => wpautop( 'Your checkout page is configured; however, it is currently using the legacy [download_checkout] shortcode. We recommend changing your checkout to use the EDD Checkout Block.

    ' ), + 'actions' => 'Edit Checkout Page', + 'test' => 'edd_missing_purchase_page', + ); + + $result = self::$test->get_test_missing_purchase_page(); + + $this->assertSame( $expected, $result ); + + // Reset the purchase page content. + wp_update_post( + array( + 'ID' => $purchase_page, + 'post_content' => $purchase_page_content, + ) + ); + } + + /** + * @covers \EDD\Admin\SiteHealth\Direct::get_test_gateways_enabled() + */ + public function test_get_test_gateways_enabled() { + $enabled_gateways = edd_get_option( 'gateways', array() ); + + edd_update_option( 'gateways', array( 'manual' ) ); + + $expected = array( + 'label' => 'You have at least one gateway enabled', + 'status' => 'good', + 'badge' => array( + 'label' => 'Easy Digital Downloads', + 'color' => 'blue', + ), + 'description' => '

    Fantastic! You have enabled a gateway and can accept orders.

    ', + 'actions' => '', + 'test' => 'edd_gateways_enabled', + ); + + $result = self::$test->get_test_gateways_enabled(); + + $this->assertSame( $expected, $result ); + + // Reset the gateways. + edd_update_option( 'gateways', $enabled_gateways ); + } + + /** + * @covers \EDD\Admin\SiteHealth\Direct::get_test_gateways_enabled() + */ + public function test_get_test_gateways_enabled_no_gateways_live_mode() { + $enabled_gateways = edd_get_option( 'gateways', array() ); + $button_url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + ) + ); + + // Remove all gateways. + edd_update_option( 'gateways', array() ); + add_filter( 'edd_is_test_mode', '__return_false' ); + + $expected = array( + 'label' => 'Your store is not accepting payments', + 'status' => 'critical', + 'badge' => array( + 'label' => 'Easy Digital Downloads', + 'color' => 'red', + ), + 'description' => + '

    To process orders that require payment, you must have a gateway enabled.

    ' . + '

    A gateway is a service, such as PayPal or Stripe, that allows your store to accept payments. ' . + 'Stores that offer multiple ways for their customers to pay see higher conversion rates.

    ', + 'actions' => 'Configure a Gateway', + 'test' => 'edd_gateways_enabled', + ); + + $result = self::$test->get_test_gateways_enabled(); + + $this->assertSame( $expected, $result ); + + // Reset the gateways. + edd_update_option( 'gateways', $enabled_gateways ); + remove_filter( 'edd_is_test_mode', '__return_false' ); + } + + /** + * @covers \EDD\Admin\SiteHealth\Direct::get_test_gateways_enabled() + */ + public function test_get_test_gateways_enabled_no_gateways_test_mode() { + $enabled_gateways = edd_get_option( 'gateways', array() ); + $button_url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + ) + ); + + // Remove all gateways. + edd_update_option( 'gateways', array() ); + add_filter( 'edd_is_test_mode', '__return_true' ); + + $expected = array( + 'label' => 'Your store is not accepting payments', + 'status' => 'recommended', + 'badge' => array( + 'label' => 'Easy Digital Downloads', + 'color' => 'gray', + ), + 'description' => + '

    To process orders that require payment, you must have a gateway enabled.

    ' . + '

    A gateway is a service, such as PayPal or Stripe, that allows your store to accept payments. ' . + 'Stores that offer multiple ways for their customers to pay see higher conversion rates.

    ', + 'actions' => 'Configure a Gateway', + 'test' => 'edd_gateways_enabled', + ); + + $result = self::$test->get_test_gateways_enabled(); + + $this->assertSame( $expected, $result ); + + // Reset the gateways. + edd_update_option( 'gateways', $enabled_gateways ); + remove_filter( 'edd_is_test_mode', '__return_true' ); + } +} diff --git a/tests/sitehealth/tests-information.php b/tests/sitehealth/tests-information.php new file mode 100644 index 00000000000..62398384f94 --- /dev/null +++ b/tests/sitehealth/tests-information.php @@ -0,0 +1,51 @@ +assertArrayHasKey( 'edd_general', $this->get_test() ); + } + + public function test_edd_tables_key_exists() { + $this->assertArrayHasKey( 'edd_tables', $this->get_test() ); + } + + public function test_edd_pages_key_exists() { + $this->assertArrayHasKey( 'edd_pages', $this->get_test() ); + } + + public function test_edd_templates_key_exists() { + $this->assertArrayHasKey( 'edd_templates', $this->get_test() ); + } + + public function test_edd_gateways_key_exists() { + $this->assertArrayHasKey( 'edd_gateways', $this->get_test() ); + } + + public function test_edd_taxes_key_exists() { + $this->assertArrayHasKey( 'edd_taxes', $this->get_test() ); + } + + public function test_edd_sessions_key_exists() { + $this->assertArrayHasKey( 'edd_sessions', $this->get_test() ); + } + + public function test_edd_cron_key_exists() { + $this->assertArrayHasKey( 'edd_cron', $this->get_test() ); + } + + private function get_test() { + if ( is_null( self::$test ) ) { + $information = new \EDD\Admin\SiteHealth\Information(); + self::$test = $information->get_data( array() ); + } + + return self::$test; + } +} diff --git a/tests/sitehealth/tests-sessions.php b/tests/sitehealth/tests-sessions.php new file mode 100644 index 00000000000..d54763efd6a --- /dev/null +++ b/tests/sitehealth/tests-sessions.php @@ -0,0 +1,35 @@ +assertEquals( 'Easy Digital Downloads — Sessions', $this->get_data()['label'] ); + } + + public function test_sessions_fields_session_enabled() { + $this->assertArrayHasKey( 'session_enabled', $this->get_data()['fields'] ); + } + + public function test_session_enabled_is_disabled() { + $this->assertEquals( 'Disabled', $this->get_data()['fields']['session_enabled']['value'] ); + } + + public function test_session_type_is_database() { + $this->assertEquals( 'Database', $this->get_data()['fields']['session_type']['value'] ); + } + + private function get_data() { + if ( is_null( self::$data ) ) { + $sessions = new \EDD\Admin\SiteHealth\Sessions(); + self::$data = $sessions->get(); + } + + return self::$data; + } +} diff --git a/tests/stripe/admin/tests-license-manager.php b/tests/stripe/admin/tests-license-manager.php new file mode 100644 index 00000000000..b50e7b7be52 --- /dev/null +++ b/tests/stripe/admin/tests-license-manager.php @@ -0,0 +1,100 @@ +set_role( 'administrator' ); + $current_user->add_cap( 'manage_shop_settings' ); + } + + public function tearDown(): void { + parent::tearDown(); + LicenseData::delete_pro_license(); + LicenseData::delete_stripe_license(); + edd_stripe()->application_fee->reset_license(); + remove_filter( 'edd_is_gateway_active', '__return_true' ); + remove_filter( 'edd_is_pro', '__return_false' ); + global $current_user; + $current_user = self::$original_user; + } + + public function test_admin_notice_valid_license_empty() { + LicenseData::get_pro_license(); + + $this->assertEmpty( $this->get_notice() ); + } + + public function test_admin_notice_expired_license_edd_is_pro_is_empty() { + remove_filter( 'edd_is_pro', '__return_false' ); + if ( ! edd_is_pro() ) { + $this->markTestSkipped( 'EDD is not Pro' ); + } + LicenseData::get_stripe_license( + array( + 'license' => 'expired', + 'expires' => date( 'Y-m-d', strtotime( '-1 month' ) ), + ) + ); + + $this->assertEmpty( $this->get_notice() ); + } + + public function test_admin_notice_expired_license() { + LicenseData::get_stripe_license( + array( + 'license' => 'expired', + 'expires' => date( 'Y-m-d', strtotime( '-1 month' ) ), + ) + ); + + $this->assertStringContainsString( 'You are now paying additional fees with every Stripe transaction.', $this->get_notice() ); + } + + public function test_admin_notice_grace_period_license() { + LicenseData::get_stripe_license( + array( + 'license' => 'expired', + 'expires' => date( 'Y-m-d', strtotime( '-1 day' ) ), + ) + ); + + $this->assertStringContainsString( 'Renew your license before', $this->get_notice() ); + } + + private function get_notice() { + $license_manager = new Manager(); + ob_start(); + $license_manager->register_admin_notices(); + + return ob_get_clean(); + } +} diff --git a/tests/stripe/admin/tests-notices-registry.php b/tests/stripe/admin/tests-notices-registry.php new file mode 100644 index 00000000000..3ddf2b48564 --- /dev/null +++ b/tests/stripe/admin/tests-notices-registry.php @@ -0,0 +1,75 @@ +registry = new \EDD_Stripe_Admin_Notices_Registry(); + } + + /** + * @covers EDD_Stripe_Admin_Notices_Registry::add() + */ + public function test_add_should_add_with_defaults() { + $notice = array( + 'type' => 'success', + 'dismissible' => true, + 'message' => 'bar', + ); + + $this->registry->add( 'foo', array( + 'message' => 'bar', + ) ); + + $this->assertEqualSets( $notice, $this->registry->get_item( 'foo' ) ); + } + + /** + * @covers EDD_Stripe_Admin_Notices_Registry::add() + */ + public function test_add_with_no_message_throws_exception() { + $this->expectException( \Exception::class ); + + $this->registry->add( 'foo', array() ); + } + + /** + * @covers EDD_Stripe_Admin_Notices_Registry::add() + */ + public function test_add_validate_type() { + $notice = array( + 'type' => 'success', + 'dismissible' => true, + 'message' => 'bar', + ); + + $this->registry->add( 'foo', array( + 'message' => 'bar', + 'type' => 'baz', + ) ); + + $this->assertEqualSets( $notice, $this->registry->get_item( 'foo' ) ); + } +} diff --git a/tests/stripe/admin/tests-notices.php b/tests/stripe/admin/tests-notices.php new file mode 100644 index 00000000000..19611b4b726 --- /dev/null +++ b/tests/stripe/admin/tests-notices.php @@ -0,0 +1,112 @@ +registry = new \EDD_Stripe_Admin_Notices_Registry(); + $this->notices = new \EDD_Stripe_Admin_Notices( $this->registry ); + } + + /** + * @covers EDD_Stripe_Admin_Notices::get_dismissed_option_name() + */ + public function test_get_dismissed_option_name() { + $this->assertEquals( 'edds_notice_foo_dismissed', $this->notices->get_dismissed_option_name( 'foo' ) ); + } + + /** + * @covers EDD_Stripe_Admin_Notices::get_dismissed_option_name() + */ + public function test_get_dismissed_option_name_backwards_compat() { + $this->assertEquals( 'edds_stripe_connect_intro_notice_dismissed', $this->notices->get_dismissed_option_name( 'stripe-connect' ) ); + } + + /** + * @covers EDD_Stripe_Admin_Notices::build() + */ + public function test_build() { + $this->registry->add( 'foo', array( + 'message' => 'bar', + 'type' => 'info', + 'dismissible' => true, + ) ); + + $output = $this->notices->build( 'foo' ); + + $this->assertContainsSelector( '#edds-foo-notice', $output ); + $this->assertContainsSelector( '.edds-admin-notice', $output ); + $this->assertContainsSelector( '.notice', $output ); + $this->assertContainsSelector( '.notice-info', $output ); + + $this->assertElementContains( 'bar', 'div', $output ); + } + + /** + * @covers EDD_Stripe_Admin_Notices::build() + */ + public function test_build_with_callable_message() { + $this->registry->add( 'foo', array( + 'message' => function() { + return 'bar'; + }, + ) ); + + $output = $this->notices->build( 'foo' ); + + $this->assertElementContains( 'bar', 'div', $output ); + } + + /** + * @covers EDD_Stripe_Admin_Notices::build() + */ + public function test_build_dismissed_notice_is_empty() { + $this->registry->add( 'foo', array( + 'message' => 'bar', + 'type' => 'info', + 'dismissible' => true, + ) ); + + $this->notices->dismiss( 'foo' ); + + $output = $this->notices->build( 'foo' ); + + $this->assertEquals( '', $output ); + } + +} diff --git a/tests/stripe/tests-application-fee.php b/tests/stripe/tests-application-fee.php new file mode 100644 index 00000000000..6278f952c54 --- /dev/null +++ b/tests/stripe/tests-application-fee.php @@ -0,0 +1,196 @@ +application_fee->reset_license(); + } + + public function test_edds_application_fee_is_3() { + $this->assertStringContainsString( '3%', edd_stripe()->application_fee->get_fee_message() ); + } + + public function test_edds_application_fee_amount_is_30() { + $this->assertEquals( 30, edd_stripe()->application_fee->get_application_fee_amount( 1000 ) ); + } + + public function test_stripe_account_not_connected_has_application_fee_is_false() { + edd_update_option( 'stripe_connect_account_id', '' ); + + $this->assertFalse( edd_stripe()->application_fee->has_application_fee() ); + + edd_update_option( 'stripe_connect_account_id', 'acct_1234567890' ); + } + + public function test_stripe_account_country_india_has_application_fee_is_false() { + edd_update_option( 'stripe_connect_account_country', 'in' ); + + $this->assertFalse( edd_stripe()->application_fee->has_application_fee() ); + + edd_update_option( 'stripe_connect_account_country', 'us' ); + } + + public function test_lifetime_pass_has_application_fee_is_false() { + LicenseData::get_pro_license(); + + $license = new \EDD\Gateways\Stripe\License(); + $this->assertFalse( $license->is_expiring_soon() ); + $this->assertFalse( edd_stripe()->application_fee->has_application_fee() ); + $this->assertEmpty( edd_stripe()->application_fee->get_fee_message() ); + $this->assertEquals( 'Valid License', edd_stripe()->application_fee->get_status() ); + } + + public function test_edd_is_pro_fee_message_says_upgrade() { + add_filter( 'edd_is_pro', '__return_false' ); + add_filter( 'wp_doing_ajax', '__return_true' ); + + $this->assertStringContainsString( 'Upgrade to Pro', edd_stripe()->application_fee->get_fee_message() ); + + remove_filter( 'edd_is_pro', '__return_false' ); + remove_filter( 'wp_doing_ajax', '__return_true' ); + } + + public function test_personal_pass_has_application_fee_is_true() { + $pro_license = LicenseData::get_pro_license( + array( + 'pass_id' => 1245715, + ) + ); + + $this->assertTrue( edd_stripe()->application_fee->has_application_fee() ); + } + + public function test_stripe_license_has_application_fee_is_false() { + LicenseData::get_stripe_license(); + + $this->assertFalse( edd_stripe()->application_fee->has_application_fee() ); + } + + public function test_personal_pass_plus_stripe_license_has_application_fee_is_false() { + LicenseData::get_pro_license( + array( + 'pass_id' => 1245715, + ) + ); + LicenseData::get_stripe_license(); + + $this->assertFalse( edd_stripe()->application_fee->has_application_fee() ); + } + + public function test_missing_pass_new_install_grace_period_has_application_fee_is_false() { + set_transient( 'edd_stripe_new_install', time() - DAY_IN_SECONDS ); + add_filter( 'wp_doing_ajax', '__return_true' ); + + // We test that the license is actually in the new install grace period in addition to checking the application fee. + $license = new \EDD\Gateways\Stripe\License(); + $this->assertTrue( $license->is_in_new_install_grace_period() ); + $this->assertFalse( edd_stripe()->application_fee->has_application_fee() ); + if ( edd_is_pro() ) { + $this->assertStringContainsString( 'You are in a grace period', edd_stripe()->application_fee->get_fee_message() ); + } + + delete_transient( 'edd_stripe_new_install' ); + remove_filter( 'wp_doing_ajax', '__return_true' ); + } + + public function test_stripe_license_expired_yesterday_has_application_fee_is_false() { + LicenseData::get_stripe_license( + array( + 'license' => 'expired', + 'expires' => date( 'Y-m-d', strtotime( '-1 day' ) ), + ) + ); + + add_filter( 'wp_doing_ajax', '__return_true' ); + + // We test that the license is actually showing as expired in addition to checking the application fee. + $license = new \EDD\Gateways\Stripe\License(); + $this->assertTrue( $license->is_expired() ); + $this->assertFalse( $license->is_expiring_soon() ); + $this->assertFalse( edd_stripe()->application_fee->has_application_fee() ); + $this->assertStringContainsString( 'you are in a grace period.', edd_stripe()->application_fee->get_fee_message() ); + $this->assertEquals( 'Grace Period', edd_stripe()->application_fee->get_status() ); + + remove_filter( 'wp_doing_ajax', '__return_true' ); + } + + public function test_stripe_license_expired_last_month_has_application_fee_is_true() { + LicenseData::get_stripe_license( + array( + 'license' => 'expired', + 'expires' => date( 'Y-m-d', strtotime( '-1 month' ) ), + ) + ); + + add_filter( 'wp_doing_ajax', '__return_true' ); + + // We test that the license is actually showing as expired in addition to checking the application fee. + $license = new \EDD\Gateways\Stripe\License(); + $this->assertTrue( $license->is_expired() ); + $this->assertTrue( edd_stripe()->application_fee->has_application_fee() ); + $this->assertStringContainsString( 'Your license expired', edd_stripe()->application_fee->get_fee_message() ); + + remove_filter( 'wp_doing_ajax', '__return_true' ); + } + + public function test_stripe_license_expiring_next_week_is_expiring_soon_is_true() { + LicenseData::get_stripe_license( + array( + 'expires' => date( 'Y-m-d', strtotime( '+1 week' ) ), + ) + ); + + $license = new \EDD\Gateways\Stripe\License(); + $this->assertTrue( $license->is_expiring_soon() ); + } + + public function test_pro_license_disabled_has_application_fee_is_true() { + $pro_license = LicenseData::get_pro_license( + array( + 'license' => 'invalid', + 'error' => 'disabled', + 'success' => false, + ) + ); + + add_filter( 'wp_doing_ajax', '__return_true' ); + + $license = new \EDD\Gateways\Stripe\License(); + $this->assertFalse( $license->is_license_valid() ); + $this->assertTrue( edd_stripe()->application_fee->has_application_fee() ); + if ( edd_is_pro() ) { + $this->assertStringContainsString( 'Activate or upgrade your license', edd_stripe()->application_fee->get_fee_message() ); + } + $this->assertEquals( 'License Error: disabled', edd_stripe()->application_fee->get_status() ); + + remove_filter( 'wp_doing_ajax', '__return_true' ); + } + + public function test_stripe_license_expired_last_month_default_sl_response_is_expired() { + LicenseData::get_stripe_license( + array( + 'success' => false, + 'license' => 'invalid', + 'error' => 'expired', + 'expires' => date( 'Y-m-d', strtotime( '-1 month' ) ), + ) + ); + + // We test that the license is actually showing as expired in addition to checking the application fee. + $license = new \EDD\Gateways\Stripe\License(); + $this->assertTrue( $license->is_expired() ); + } +} diff --git a/tests/stripe/tests-functions.php b/tests/stripe/tests-functions.php new file mode 100644 index 00000000000..07a520fc382 --- /dev/null +++ b/tests/stripe/tests-functions.php @@ -0,0 +1,96 @@ +assertTrue( edds_truthy_to_bool( 1 ) ); + $this->assertTrue( edds_truthy_to_bool( 'yes' ) ); + $this->assertTrue( edds_truthy_to_bool( 'Yes' ) ); + $this->assertTrue( edds_truthy_to_bool( 'YES' ) ); + $this->assertTrue( edds_truthy_to_bool( 'true' ) ); + $this->assertTrue( edds_truthy_to_bool( 'True' ) ); + $this->assertTrue( edds_truthy_to_bool( 'TRUE' ) ); + $this->assertFalse( edds_truthy_to_bool( 0 ) ); + $this->assertFalse( edds_truthy_to_bool( 'no' ) ); + $this->assertFalse( edds_truthy_to_bool( 'No' ) ); + $this->assertFalse( edds_truthy_to_bool( 'NO' ) ); + $this->assertFalse( edds_truthy_to_bool( 'false' ) ); + $this->assertFalse( edds_truthy_to_bool( 'False' ) ); + $this->assertFalse( edds_truthy_to_bool( 'FALSE' ) ); + } + + /** + * @covers ::edds_stripe_connect_account_country_supports_application_fees + */ + public function test_edds_stripe_connect_account_country_supports_application_fees() { + edd_update_option( 'stripe_connect_account_country', 'us' ); + + $this->assertTrue( + edds_stripe_connect_account_country_supports_application_fees() + ); + } + + /** + * @covers ::edds_stripe_connect_account_country_supports_application_fees + */ + public function test_edds_stripe_connect_blank_account_country_supports_application_fees() { + $this->assertTrue( + edds_stripe_connect_account_country_supports_application_fees() + ); + } + + /** + * @covers ::edds_stripe_connect_account_country_supports_application_fees + */ + public function test_edds_stripe_connect_account_country_does_not_support_application_fees() { + edd_update_option( 'stripe_connect_account_country', 'br' ); + + $this->assertFalse( + edds_stripe_connect_account_country_supports_application_fees() + ); + } + + /** + * @covers ::edds_is_zero_decimal_currency + */ + public function test_edds_is_zero_decimal_currency() { + edd_update_option( 'currency', 'JPY' ); + $this->assertTrue( edds_is_zero_decimal_currency() ); + } + + /** + * @covers ::edds_is_zero_decimal_currency + */ + public function test_edds_is_non_zero_decimal_currency() { + edd_update_option( 'currency', 'USD' ); + $this->assertFalse( edds_is_zero_decimal_currency() ); + } + + /** + * @covers ::edds_api_request + */ + public function test_request() { + $this->expectException( \EDD\Vendor\Stripe\Exception\AuthenticationException::class ); + + edds_api_request( 'Customer', 'retrieve', 123 ); + } + + /** + * @covers ::edds_api_request + */ + public function test_invalid_request() { + $this->expectException( \EDD_Stripe_Utils_Exceptions_Stripe_Object_Not_Found::class ); + + edds_api_request( 'UnknownObject', 'retrieve', 123 ); + } +} diff --git a/tests/stripe/tests-payment-elements.php b/tests/stripe/tests-payment-elements.php new file mode 100644 index 00000000000..9796264aae5 --- /dev/null +++ b/tests/stripe/tests-payment-elements.php @@ -0,0 +1,125 @@ +assertEquals( 'stripe',edds_get_stripe_payment_elements_theme() ); + } + + public function test_default_payment_elements_variables() { + $this->assertEmpty( edds_get_stripe_payment_elements_variables() ); + } + + public function test_payment_elements_rules() { + $this->assertEmpty( edds_get_stripe_payment_elements_rules() ); + } + + public function test_default_payment_elements_layout() { + $expected = array( + 'type' => 'tabs', + 'defaultCollapsed' => false, + ); + + $this->assertSame( $expected, edds_get_stripe_payment_elements_layout() ); + } + + public function test_default_payment_elements_wallets() { + $expected = array( + 'applePay' => 'auto', + 'googlePay' => 'auto', + ); + + $this->assertSame( $expected, edds_get_stripe_payment_elements_wallets() ); + } + + public function test_disable_payment_elements_wallets() { + $expected = array( + 'applePay' => 'never', + 'googlePay' => 'never', + ); + + add_filter( 'edds_stripe_payment_elements_disable_wallets', '__return_true' ); + + $this->assertSame( $expected, edds_get_stripe_payment_elements_wallets() ); + + remove_filter( 'edds_stripe_payment_elements_disable_wallets', '__return_true' ); + } + + public function test_default_payment_elements_label_style() { + $this->assertEquals( 'above', edds_get_stripe_payment_elements_label_style() ); + } + + public function test_default_payment_elements_fonts() { + $this->assertEmpty( edds_get_stripe_payment_elements_fonts() ); + } + + public function test_default_payment_elements_fields() { + $expected = array( + 'billingDetails' => array( + 'name' => 'auto', + 'email' => 'never', // It is not advised to change this to auto, as it will create duplicate email fields on checkout. + 'phone' => 'never', + 'address' => 'never', + ), + ); + + $this->assertSame( $expected, edds_get_stripe_payment_elements_fields() ); + } + + public function test_default_payment_elements_terms() { + $this->assertSame( array( 'card' => 'auto' ), edds_get_stripe_payment_elements_terms() ); + } + + public function test_default_payment_elements_payment_method_types() { + $this->assertEmpty( edds_payment_element_payment_method_types() ); + } + + public function test_payment_method_types() { + $this->assertIsArray( \EDD\Gateways\Stripe\PaymentMethods::list() ); + } + + public function test_payment_method_types_includes_alipay() { + $methods = \EDD\Gateways\Stripe\PaymentMethods::list(); + + $this->assertArrayHasKey( 'alipay', $methods ); + } + + public function test_payment_method_types_get_label() { + $this->assertEquals( 'Cartes Bancaires', \EDD\Gateways\Stripe\PaymentMethods::get_label( 'cartes_bancaires' ) ); + } + + public function test_payment_method_types_get_label_invalid_is_empty() { + $this->assertEmpty( \EDD\Gateways\Stripe\PaymentMethods::get_label( 'fake_type' ) ); + } + + public function test_order_with_payment_method_returns_label() { + $order = parent::edd()->order->create_and_get(); + + edd_add_order_meta( $order->id, 'stripe_payment_method_type', 'alipay' ); + + $this->assertEquals( 'Alipay', edd_get_gateway_checkout_label( 'stripe', $order ) ); + $this->assertEquals( 'Stripe (Alipay)', edd_get_gateway_admin_label( 'stripe', $order ) ); + } + + public function test_order_without_payment_method_returns_default_label() { + $order = parent::edd()->order->create_and_get(); + + $this->assertEquals( 'Credit Card', edd_get_gateway_checkout_label( 'stripe', $order ) ); + $this->assertEquals( 'Stripe', edd_get_gateway_admin_label( 'stripe', $order ) ); + } + + public function test_order_with_invalid_payment_method_returns_default_label() { + $order = parent::edd()->order->create_and_get(); + + edd_add_order_meta( $order->id, 'stripe_payment_method_type', 'not_a_method' ); + + $this->assertEquals( 'Credit Card', edd_get_gateway_checkout_label( 'stripe', $order ) ); + $this->assertEquals( 'Stripe', edd_get_gateway_admin_label( 'stripe', $order ) ); + } +} diff --git a/tests/stripe/tests-rate-limiting.php b/tests/stripe/tests-rate-limiting.php new file mode 100644 index 00000000000..993dd171684 --- /dev/null +++ b/tests/stripe/tests-rate-limiting.php @@ -0,0 +1,77 @@ +assertTrue( edd_stripe()->rate_limiting->card_error_checks_enabled() ); + } + + public function test_card_error_tracking_off_with_filter() { + add_filter( 'edds_card_error_checking_enabled', '__return_false' ); + $this->assertFalse( edd_stripe()->rate_limiting->card_error_checks_enabled() ); + remove_filter( 'edds_card_error_checking_enabled', '__return_false' ); + } + + public function test_card_errors_id() { + $this->assertSame( '127.0.0.1', edd_stripe()->rate_limiting->get_card_error_id() ); + } + + public function test_card_error_limit_false() { + $this->assertFalse( edd_stripe()->rate_limiting->has_hit_card_error_limit() ); + } + + public function test_card_error_limit_true() { + edd_stripe()->rate_limiting->write_to_log( array( '127.0.0.1' => array( 'count' => 5, 'timeout' => current_time( 'timestamp' ) + 5 ) ) ); + $this->assertTrue( edd_stripe()->rate_limiting->has_hit_card_error_limit() ); + edd_stripe()->rate_limiting->remove_log_entry( '127.0.0.1' ); + } + + public function test_card_error_limit_false_past_expiration() { + edd_stripe()->rate_limiting->write_to_log( array( '127.0.0.1' => array( 'count' => 5, 'timeout' => current_time( 'timestamp' ) - 5 ) ) ); + $this->assertFalse( edd_stripe()->rate_limiting->has_hit_card_error_limit() ); + $this->assertTrue( empty( edd_stripe()->rate_limiting->get_rate_limiting_entry( '127.0.0.1' ) ) ); + } + + public function test_increment_card_errors() { + $this->assertSame( 1, edd_stripe()->rate_limiting->increment_card_error_count() ); + $this->assertSame( 2, edd_stripe()->rate_limiting->increment_card_error_count() ); + + $entry = edd_stripe()->rate_limiting->get_rate_limiting_entry( '127.0.0.1' ); + + $this->assertSame( 2, $entry['count'] ); + + edd_stripe()->rate_limiting->remove_log_entry( '127.0.0.1' ); + } + + public function test_card_form_not_at_error_limit() { + $card_form = edds_credit_card_form( false ); + $this->assertStringContainsString( '
    ', $card_form ); + } + + public function test_card_form_at_error_limit() { + edd_stripe()->rate_limiting->write_to_log( array( '127.0.0.1' => array( 'count' => 5, 'timeout' => current_time( 'timestamp' ) + 5 ) ) ); + $card_form = edds_credit_card_form( false ); + $this->assertEquals( '', $card_form ); + $errors = edd_get_errors(); + $this->assertContains( 'We are unable to process your payment at this time, please try again later or contact support.', $errors ); + edd_stripe()->rate_limiting->remove_log_entry( '127.0.0.1' ); + } + + public function test_card_max_attempts_filter() { + add_filter( 'edds_max_card_error_count', function() { return 2; } ); + edd_stripe()->rate_limiting->increment_card_error_count(); + edd_stripe()->rate_limiting->increment_card_error_count(); + + $this->assertTrue( edd_stripe()->rate_limiting->has_hit_card_error_limit() ); + remove_filter( 'edds_max_card_error_count', function() { return 2; } ); + } + +} diff --git a/tests/stripe/tests-regional-support.php b/tests/stripe/tests-regional-support.php new file mode 100644 index 00000000000..6aa60b9b58e --- /dev/null +++ b/tests/stripe/tests-regional-support.php @@ -0,0 +1,45 @@ +has_regional_support = null; + edd_stripe()->regional_support = null; + } + + public function test_us_base_country_regional_support_is_false() { + edd_update_option( 'base_country', 'US' ); + + $this->assertFalse( edd_stripe()->has_regional_support() ); + } + + public function test_us_connect_country_regional_support_is_false() { + edd_update_option( 'stripe_connect_account_country', 'us' ); + + $this->assertFalse( edd_stripe()->has_regional_support() ); + } + + public function test_in_base_country_regional_support_is_true() { + edd_update_option( 'base_country', 'IN' ); + + $this->assertTrue( edd_stripe()->has_regional_support() ); + $this->assertTrue( edd_stripe()->regional_support->requires_card_name ); + } + + public function test_in_connect_country_regional_support_is_true() { + edd_update_option( 'base_country', 'US' ); + edd_update_option( 'stripe_connect_account_country', 'in' ); + + $this->assertTrue( edd_stripe()->has_regional_support() ); + $this->assertTrue( edd_stripe()->regional_support->requires_card_name ); + } +} diff --git a/tests/stripe/tests-statement-descriptors.php b/tests/stripe/tests-statement-descriptors.php new file mode 100644 index 00000000000..68c44194dd5 --- /dev/null +++ b/tests/stripe/tests-statement-descriptors.php @@ -0,0 +1,66 @@ +assertEmpty( StatementDescriptor::sanitize_suffix() ); + } + + public function test_purchase_summary_enabled_length_valid() { + edd_update_option( 'stripe_statement_descriptor_prefix', 'EDDTEST' ); + edd_update_option( 'stripe_include_purchase_summary_in_statement_descriptor', '1' ); + + $purchase_summary = 'Product Name'; + $suffix = StatementDescriptor::sanitize_suffix( $purchase_summary ); + + $this->assertEquals( 'PRODUCTNAME', $suffix ); + } + + public function test_purchase_summary_enabled_length_invalid() { + edd_update_option( 'stripe_statement_descriptor_prefix', 'EDDTEST' ); + edd_update_option( 'stripe_include_purchase_summary_in_statement_descriptor', '1' ); + + $purchase_summary = 'Product Name That Is Too Long'; + $suffix = StatementDescriptor::sanitize_suffix( $purchase_summary ); + + $this->assertEquals( 'PRODUCTNAMETH', $suffix ); + } + + public function test_suffix_with_no_latin_character() { + edd_update_option( 'stripe_statement_descriptor_prefix', '12345' ); + edd_update_option( 'stripe_include_purchase_summary_in_statement_descriptor', '1' ); + + $purchase_summary = '987'; + $suffix = StatementDescriptor::sanitize_suffix( $purchase_summary ); + + $this->assertEquals( 'E-987', $suffix ); + } + + public function test_suffix_prefix_has_latin_character() { + edd_update_option( 'stripe_statement_descriptor_prefix', 'EDDTEST' ); + edd_update_option( 'stripe_include_purchase_summary_in_statement_descriptor', '1' ); + + $purchase_summary = '987'; + $suffix = StatementDescriptor::sanitize_suffix( $purchase_summary ); + + $this->assertEquals( '987', $suffix ); + } + + public function test_suffix_with_unsupported_characters() { + edd_update_option( 'stripe_statement_descriptor_prefix', 'EDDTEST' ); + edd_update_option( 'stripe_include_purchase_summary_in_statement_descriptor', '1' ); + + $purchase_summary = 'N\\m*"e Test'; + $suffix = StatementDescriptor::sanitize_suffix( $purchase_summary ); + + $this->assertEquals( 'NAMETEST', $suffix ); + } +} diff --git a/tests/stripe/tests-stripe-api.php b/tests/stripe/tests-stripe-api.php new file mode 100644 index 00000000000..a28681835ff --- /dev/null +++ b/tests/stripe/tests-stripe-api.php @@ -0,0 +1,79 @@ +set_api_key(); + + $this->assertSame( 'sk_test_123', Stripe::$apiKey ); + } + + /** + * @covers ::set_api_key + */ + public function test_set_api_key_live() { + edd_update_option( 'test_mode', false ); + $api = new EDD_Stripe_API; + $api->set_api_key(); + + $this->assertSame( 'sk_live_123', Stripe::$apiKey ); + } + + /** + * @covers ::set_app_info + */ + public function test_set_app_info() { + $api = new EDD_Stripe_API(); + $api->set_app_info(); + + $appinfo = array( + 'WordPress Easy Digital Downloads - Stripe', + EDD_VERSION, + esc_url( site_url() ), + EDD_STRIPE_PARTNER_ID, + ); + + $this->assertEqualSets( $appinfo, Stripe::$appInfo ); + } + + public function test_set_api_version() { + $api = new EDD_Stripe_API; + $api->set_api_version(); + + $this->assertSame( EDD_STRIPE_API_VERSION, Stripe::$apiVersion ); + } + + /** + * @covers ::set_api_version + */ + public function test_no_conflict() { + // Mock some other plugin making requests. + Stripe::setApiVersion( '123' ); + + $api = new EDD_Stripe_API; + $api->set_api_version(); + + $this->assertSame( EDD_STRIPE_API_VERSION, Stripe::$apiVersion ); + } +} 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..3e72a8454fc --- /dev/null +++ b/tests/taxes/tests-tax-rates.php @@ -0,0 +1,127 @@ + '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 ); + } + + /** + * @covers ::edd_get_tax_rate_by_location + */ + 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..c57b6baf45f --- /dev/null +++ b/tests/tests-api.php @@ -0,0 +1,672 @@ +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 ); + self::$payment = edd_get_payment( self::$payment_id ); + + // We don't want to trigger purchase receipts here. + remove_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999 ); + edd_update_payment_status( self::$payment_id, 'complete' ); + // Now add it back. + add_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999 ); + + 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 ); + + // Generate a file download log. + edd_record_download_in_log( self::$post->ID, 0, array(), '127.0.0.1', self::$payment_id, 'EDD\Tests' ); + } + + 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 static function tearDownAfterClass(): void { + parent::tearDownAfterClass(); + unset( $_SERVER['REMOTE_ADDR'] ); + } + + 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_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( 1, $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'] ); + } + + public function test_file_download_logs_generic() { + try { + $out = EDD()->api->get_download_logs(); + + $this->assertArrayHasKey( 'download_logs', $out ); + + $logs = $out['download_logs']; + + $this->assertEquals( 1, count( $logs ) ); + + $this->assertArrayHasKey( 'ID', $logs[0] ); + $this->assertArrayHasKey( 'user_id', $logs[0] ); + $this->assertArrayHasKey( 'product_id', $logs[0] ); + $this->assertArrayHasKey( 'product_name', $logs[0] ); + $this->assertArrayHasKey( 'customer_id', $logs[0] ); + $this->assertArrayHasKey( 'payment_id', $logs[0] ); + $this->assertArrayHasKey( 'file', $logs[0] ); + $this->assertArrayHasKey( 'ip', $logs[0] ); + $this->assertArrayHasKey( 'date', $logs[0] ); + + } catch ( \WPDieException $e ) {} + } + + public function test_file_download_logs_by_customer_id() { + try { + $out = EDD()->api->get_download_logs( self::$payment->customer_id ); + + $this->assertArrayHasKey( 'download_logs', $out ); + + $logs = $out['download_logs']; + + $this->assertEquals( 1, count( $logs ) ); + + $this->assertArrayHasKey( 'ID', $logs[0] ); + $this->assertArrayHasKey( 'user_id', $logs[0] ); + $this->assertArrayHasKey( 'product_id', $logs[0] ); + $this->assertArrayHasKey( 'product_name', $logs[0] ); + $this->assertArrayHasKey( 'customer_id', $logs[0] ); + $this->assertArrayHasKey( 'payment_id', $logs[0] ); + $this->assertArrayHasKey( 'file', $logs[0] ); + $this->assertArrayHasKey( 'ip', $logs[0] ); + $this->assertArrayHasKey( 'date', $logs[0] ); + + } catch ( \WPDieException $e ) {} + } + + public function test_file_download_logs_by_customer_email() { + try { + $out = EDD()->api->get_download_logs( self::$payment->email ); + + $this->assertArrayHasKey( 'download_logs', $out ); + + $logs = $out['download_logs']; + + $this->assertEquals( 1, count( $logs ) ); + + $this->assertArrayHasKey( 'ID', $logs[0] ); + $this->assertArrayHasKey( 'user_id', $logs[0] ); + $this->assertArrayHasKey( 'product_id', $logs[0] ); + $this->assertArrayHasKey( 'product_name', $logs[0] ); + $this->assertArrayHasKey( 'customer_id', $logs[0] ); + $this->assertArrayHasKey( 'payment_id', $logs[0] ); + $this->assertArrayHasKey( 'file', $logs[0] ); + $this->assertArrayHasKey( 'ip', $logs[0] ); + $this->assertArrayHasKey( 'date', $logs[0] ); + + } catch ( \WPDieException $e ) {} + } + + public function test_file_download_logs_by_invalid_customer_id() { + try { + $out = EDD()->api->get_download_logs( 99999 ); + + $this->assertArrayHasKey( 'error', $out ); + $this->assertSame( 'No download logs found!', $out['error'] ); + + } catch ( \WPDieException $e ) {} + } + +} diff --git a/tests/tests-cart.php b/tests/tests-cart.php new file mode 100644 index 00000000000..c96d7025812 --- /dev/null +++ b/tests/tests-cart.php @@ -0,0 +1,771 @@ +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, + ) ); + + $contents = edd_get_cart_contents(); + $cart_item = reset( $contents ); + + $this->assertEquals( self::$download->ID, $cart_item['id'] ); + $this->assertEquals( 0, $cart_item['options']['price_id'] ); + } + + public function test_get_cart_content_details() { + edd_add_to_cart( self::$download->ID, array( + 'price_id' => 0, + ) ); + + $details = edd_get_cart_content_details(); + $cart_item = reset( $details ); + + $this->assertEquals( self::$download->ID, $cart_item['id'] ); + $this->assertEquals( 20.0, $cart_item['item_price'] ); + $this->assertEquals( 20.0, $cart_item['price'] ); + $this->assertEquals( 0, $cart_item['tax'] ); + $this->assertEquals( 0, $cart_item['item_number']['options']['price_id'] ); + $this->assertTrue( array_key_exists( 'hash', $cart_item['item_number'] ) ); + } + + public function test_get_cart_content_details_with_discount() { + + edd_add_to_cart( self::$download->ID, array( + 'price_id' => 0, + ) ); + + // Now set a discount and test again + edd_set_cart_discount( '20OFF' ); + + $details = edd_get_cart_content_details(); + $cart_item = reset( $details ); + + $this->assertEquals( self::$download->ID, $cart_item['id'] ); + $this->assertEquals( 20.0, $cart_item['item_price'] ); + $this->assertEquals( 4.0, $cart_item['discount'] ); + $this->assertEquals( 16.0, $cart_item['price'] ); + $this->assertEquals( 0, $cart_item['tax'] ); + $this->assertEquals( 0, $cart_item['item_number']['options']['price_id'] ); + $this->assertTrue( array_key_exists( 'hash', $cart_item['item_number'] ) ); + } + + public function test_get_cart_content_details_with_discount_and_taxes() { + + edd_add_to_cart( self::$download->ID, array( + 'price_id' => 0, + ) ); + edd_set_cart_discount( '20OFF' ); + + // 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; + } ); + + $details = edd_get_cart_content_details(); + $cart_item = reset( $details ); + + $this->assertEquals( self::$download->ID, $cart_item['id'] ); + $this->assertEquals( 20.0, $cart_item['item_price'] ); + $this->assertEquals( 4.0, $cart_item['discount'] ); + $this->assertEquals( 19.2, $cart_item['price'] ); + $this->assertEquals( 3.2, $cart_item['tax'] ); + $this->assertEquals( 0, $cart_item['item_number']['options']['price_id'] ); + $this->assertTrue( array_key_exists( 'hash', $cart_item['item_number'] ) ); + } + + public function test_edd_get_cart_content_details_taxes_no_discount() { + + edd_add_to_cart( self::$download->ID, array( + 'price_id' => 0, + ) ); + + 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; + } ); + + $details = edd_get_cart_content_details(); + $cart_item = reset( $details ); + + $this->assertEquals( self::$download->ID, $cart_item['id'] ); + $this->assertEquals( 20.0, $cart_item['item_price'] ); + $this->assertEquals( 0, $cart_item['discount'] ); + $this->assertEquals( 24.0, $cart_item['price'] ); + $this->assertEquals( 4.0, $cart_item['tax'] ); + $this->assertEquals( 0, $cart_item['item_number']['options']['price_id'] ); + $this->assertTrue( array_key_exists( 'hash', $cart_item['item_number'] ) ); + } + + 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() ); + } + + public function test_is_cart_saved_logged_in_user_is_true() { + // Create a saved cart then test again + $cart = array( + array( + 'id' => self::$download->ID, + 'options' => array( + 'price_id' => 0, + ), + 'quantity' => 1, + ), + ); + update_user_meta( get_current_user_id(), 'edd_saved_cart', $cart ); + EDD()->session->set( 'edd_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 ) ); + } + + public function test_edd_process_add_to_cart_invalid_data_price_id_is_empty() { + edd_empty_cart(); + edd_process_add_to_cart( + array( + 'edd_options' => array( + 'price_id' => array( + 1, + ), + ), + 'edd_download_quantity' => 1, + ) + ); + $this->assertTrue( edd_is_cart_empty() ); + } + + public function test_edd_process_add_to_cart_invalid_price_id_adds_default_price_id() { + edd_empty_cart(); + edd_add_to_cart( + self::$download_2->ID, + array( + 'price_id' => 45, + ) + ); + $cart_contents = edd_get_cart_contents(); + $cart_item = reset( $cart_contents ); + $this->assertEquals( 0, $cart_item['options']['price_id'] ); + } + + public function test_adding_discount_to_cart_and_removing_it_removes_from_session() { + edd_add_to_cart( self::$download->ID, array( + 'price_id' => 0, + ) ); + + edd_set_cart_discount( '20OFF' ); + $this->assertTrue( in_array( '20OFF', explode( '|', EDD()->session->get( 'cart_discounts' ) ) ) ); + + EDD()->cart->remove_discount( '20OFF' ); + $this->assertEmpty( EDD()->session->get( 'cart_discounts' ) ); + } +} diff --git a/tests/tests-countries.php b/tests/tests-countries.php new file mode 100644 index 00000000000..d78adf00630 --- /dev/null +++ b/tests/tests-countries.php @@ -0,0 +1,449 @@ + '', + '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() ); + } + + public function test_get_legacy_gb_state_returns_name() { + $this->assertEquals( 'Barnsley', edd_get_state_name( 'GB', 'GB-BNS' ) ); + } + + public function test_get_bj_region_state_returns_name() { + $this->assertEquals( 'Alibori', edd_get_state_name( 'BJ', 'AL' ) ); + } +} diff --git a/tests/tests-easy-digital-downloads.php b/tests/tests-easy-digital-downloads.php new file mode 100755 index 00000000000..6a26909d178 --- /dev/null +++ b/tests/tests-easy-digital-downloads.php @@ -0,0 +1,176 @@ +object = EDD(); + } + + public function tearDown(): void { + parent::tearDown(); + } + + public function test_edd_instance() { + $this->assertClassHasStaticAttribute( 'instance', 'Easy_Digital_Downloads' ); + } + + 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 + * @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/deprecated/classes.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/deprecated/classes.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-roles.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-stats.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/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/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 + * @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-errors.php b/tests/tests-errors.php new file mode 100755 index 00000000000..f2b0bff341f --- /dev/null +++ b/tests/tests-errors.php @@ -0,0 +1,43 @@ +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->assertEmpty( 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..e3b38a11e7c --- /dev/null +++ b/tests/tests-filters.php @@ -0,0 +1,328 @@ +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['edd_before_checkout_cart'][5] ); + $this->assertArrayHasKey( 'edd_listen_for_paypal_ipn', $wp_filter['init'][10] ); + $this->assertArrayHasKey( 'edd_setup_download_taxonomies', $wp_filter['init'][0] ); + $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_view_receipt() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_render_receipt_in_browser', $wp_filter['edd_view_receipt'][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..902c8f2be7a --- /dev/null +++ b/tests/tests-formatting.php @@ -0,0 +1,207 @@ +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_format_amount_data() { + $this->assertEquals( '20000.20', edd_format_amount( '20000.20', true, '', 'data' ) ); + + edd_update_option( 'thousands_separator', '.' ); + edd_update_option( 'decimal_separator', ',' ); + + $this->assertEquals( '20000.20', edd_format_amount( '20000.20', true, '', 'data' ) ); + + edd_update_option( 'thousands_separator', ' ' ); + edd_update_option( 'decimal_separator', '.' ); + + $this->assertEquals( '20000.20', edd_format_amount( '20000.20', true, '', 'data' ) ); + } + + public function test_format_amount_null() { + $this->assertEquals( '0.00', edd_format_amount( null, true, '', 'data' ) ); + } + + public function test_currency_filter() { + $this->assertEquals( '$20,000.20', edd_currency_filter( '20,000.20' ) ); + } + + public function test_currency_filter_invalid_currency() { + // Prepare test data + $price = '19.99'; + $currency = ''; + + // Call the function with the test data + $result = edd_currency_filter( $price, $currency ); + + // Assert: ensure that the image tag is escaped in the result + $this->assertStringNotContainsString( 'assertStringNotContainsString( 'ONERROR="', $result ); + $this->assertStringContainsString( strtoupper( '<img src="image.jpg" onerror="alert('0')">' ), $result ); + } + + 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() { + + edd_update_option( 'thousands_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 ); + + 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..2fa87d74efc --- /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-install.php b/tests/tests-install.php new file mode 100755 index 00000000000..7e6fa445cd7 --- /dev/null +++ b/tests/tests-install.php @@ -0,0 +1,240 @@ +assertArrayHasKey( 'purchase_page', $edd_options ); + $this->assertArrayHasKey( 'success_page', $edd_options ); + $this->assertArrayHasKey( 'failure_page', $edd_options ); + } + + /** + * Test the install function, installing pages and setting option values. + * + * @since 2.2.4 + */ + public function test_install() { + + global $edd_options; + + $origin_edd_options = $edd_options; + $origin_upgraded_from = get_option( 'edd_version_upgraded_from' ); + $origin_edd_version = edd_get_db_version(); + + parent::_delete_all_edd_data(); + edd_install(); + + // Test that function exists + $this->assertTrue( function_exists( 'edd_create_protection_files' ) ); + + // Test the edd_version_upgraded_from value + $this->assertFalse( get_option( 'edd_version_upgraded_from' ) ); + + // Test that new pages are created, and not the same as the already created ones. + // This is to make sure the test is giving the most accurate results. + $new_settings = get_option( 'edd_settings' ); + + $this->assertArrayHasKey( 'purchase_page', $new_settings ); + $this->assertNotEquals( $origin_edd_options['purchase_page'], $new_settings['purchase_page'] ); + $this->assertArrayHasKey( 'success_page', $new_settings ); + $this->assertNotEquals( $origin_edd_options['success_page'], $new_settings['success_page'] ); + $this->assertArrayHasKey( 'failure_page', $new_settings ); + $this->assertNotEquals( $origin_edd_options['failure_page'], $new_settings['failure_page'] ); + $this->assertArrayHasKey( 'purchase_history_page', $new_settings ); + $this->assertNotEquals( $origin_edd_options['purchase_history_page'], $new_settings['purchase_history_page'] ); + + $this->assertArrayHasKey( 'confirmation_page', $new_settings ); + + $this->assertEquals( edd_format_db_version( EDD_VERSION ), get_option( 'edd_version' ) ); + + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_manager' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_accountant' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_worker' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_vendor' ) ); + + // Reset to original data. + wp_delete_post( $new_settings['purchase_page'], true ); + wp_delete_post( $new_settings['success_page'], true ); + wp_delete_post( $new_settings['purchase_history_page'], true ); + wp_delete_post( $new_settings['failure_page'], true ); + update_option( 'edd_version_upgraded_from', $origin_upgraded_from ); + $edd_options = $origin_edd_options; + update_option( 'edd_settings', $edd_options ); + update_option( 'edd_version', $origin_edd_version ); + } + + public function test_edd_upgrades_have_completed_upgrade_payment_taxes_is_true() { + $this->assertTrue( edd_has_upgrade_completed( 'upgrade_payment_taxes' ) ); + } + + public function test_edd_upgrades_have_completed_migrate_orders_is_true() { + $this->assertTrue( edd_has_upgrade_completed( 'migrate_orders' ) ); + } + + /** + * Test that the install doesn't redirect when activating multiple plugins. + * + * @since 2.2.4 + */ + public function test_install_bail() { + + $_GET['activate-multi'] = 1; + + edd_install(); + + $this->assertFalse( get_transient( 'activate-multi' ) ); + + } + + /** + * Test edd_after_install(). Test that the transient gets deleted. + * + * Since 2.2.4 + */ + public function test_edd_ater_install() { + + // Prepare for test + set_transient( '_edd_installed', $GLOBALS['edd_options'], 30 ); + + // Fake admin screen + set_current_screen( 'dashboard' ); + + $this->assertNotFalse( get_transient( '_edd_installed' ) ); + + edd_after_install(); + + $this->assertFalse( get_transient( '_edd_installed' ) ); + + } + + /** + * Test that when not in admin, the function bails. + * + * @since 2.2.4 + */ + public function test_edd_after_install_bail_no_admin() { + + // Prepare for test + set_current_screen( 'front' ); + set_transient( '_edd_installed', $GLOBALS['edd_options'], 30 ); + + edd_after_install(); + $this->assertNotFalse( get_transient( '_edd_installed' ) ); + + } + + + /** + * Test that edd_after_install() bails when transient doesn't exist. + * Kind of a useless test, but for coverage :-) + * + * @since 2.2.4 + */ + public function test_edd_after_install_bail_transient() { + + // Fake admin screen + set_current_screen( 'dashboard' ); + + delete_transient( '_edd_installed' ); + + $this->assertNull( edd_after_install() ); + + // Reset to origin + set_transient( '_edd_installed', $GLOBALS['edd_options'], 30 ); + + } + + /** + * Test that edd_install_roles_on_network() bails when $wp_roles is no object. + * Kind of a useless test, but for coverage :-) + * + * @since 2.2.4 + */ + public function test_edd_install_roles_on_network_bail_object() { + + global $wp_roles; + + $origin_roles = $wp_roles; + + $wp_roles = null; + + $this->assertNull( edd_install_roles_on_network() ); + + // Reset to origin + $wp_roles = $origin_roles; + + } + + /** + * Test that edd_install_roles_on_network() creates the roles when 'shop_manager' is not defined. + * + * @since 2.2.4 + */ + public function test_edd_install_roles_on_network() { + + global $wp_roles; + + $origin_roles = $wp_roles; + + // Prepare variables for test + unset( $wp_roles->roles['shop_manager'] ); + + edd_install_roles_on_network(); + + // Test that the roles are created + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_manager' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_accountant' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_worker' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_vendor' ) ); + + + // Reset to origin + $wp_roles = $origin_roles; + + } + + /** + * Test that edd_install_roles_on_network() creates the roles when $wp_roles->roles is initially false. + * + * @since 2.6.3 + */ + public function test_edd_install_roles_on_network_when_roles_false() { + + global $wp_roles; + + $origin_roles = $wp_roles->roles; + + // Prepare variables for test + $wp_roles->roles = false; + + edd_install_roles_on_network(); + + // Test that the roles are created + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_manager' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_accountant' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_worker' ) ); + $this->assertInstanceOf( 'WP_Role', get_role( 'shop_vendor' ) ); + + + // Reset to origin + $wp_roles->roles = $origin_roles; + + } + + public function test_automatic_upgrade_updates_edd_version() { + update_option( 'edd_version', '2.1' ); + edd_do_automatic_upgrades(); + + $this->assertEquals( '2.1', get_option( 'edd_version_upgraded_from' ) ); + $this->assertEquals( edd_format_db_version( EDD_VERSION ), get_option( 'edd_version' ) ); + } +} diff --git a/tests/tests-languages.php b/tests/tests-languages.php new file mode 100644 index 00000000000..3890943eef4 --- /dev/null +++ b/tests/tests-languages.php @@ -0,0 +1,15 @@ +assertTrue( file_exists( EDD_PLUGIN_DIR . '/languages/easy-digital-downloads.pot' ) ); + } + +} diff --git a/tests/tests-license-upgrade-notice.php b/tests/tests-license-upgrade-notice.php new file mode 100644 index 00000000000..cd793cbd04e --- /dev/null +++ b/tests/tests-license-upgrade-notice.php @@ -0,0 +1,295 @@ +set_role( 'administrator' ); + wp_update_user( array( 'ID' => 1, 'first_name' => 'Admin', 'last_name' => 'User' ) ); + } + + public function tearDown(): void { + parent::tearDown(); + add_filter( 'edd_is_pro', '__return_true' ); + } + + /** + * Asserts that a notice contains a string of text. + * + * @param string $contains + * @param Notice $notice + */ + private function assertNoticeContains( $contains, Notice $notice ) { + ob_start(); + $notice->display(); + $notice_content = ob_get_clean(); + + $this->assertTrue( false !== strpos( strtolower( $notice_content ), strtolower( $contains ) ) ); + } + + /** + * No license keys activated at all. + * + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_should_display + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_display + */ + public function test_notice_should_display_if_no_license_keys() { + $notice = new License_Upgrade_Notice(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'You are using the free version of Easy Digital Downloads', $notice ); + } + + /** + * We have a license key entered, but pass data hasn't been parsed yet. + * + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_should_display + */ + public function test_notice_not_display_if_license_but_no_pass_data_yet() { + // Simulate that we have a license key. + global $edd_licensed_products; + $edd_licensed_products = array( 'license_key' ); + + $notice = new License_Upgrade_Notice(); + $this->assertFalse( $notice->should_display() ); + } + + /** + * Individual license key activated. + * + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_should_display + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_display + */ + public function test_individual_license_activated() { + // We have pass data, but no passes. + update_option( 'edd_pass_licenses', json_encode( array() ) ); + + // Simulate that we have a license key though. + global $edd_licensed_products; + $edd_licensed_products = array( 'license_key' ); + + $notice = new License_Upgrade_Notice(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'For access to additional Easy Digital Downloads extensions to grow your store', $notice ); + } + + /** + * Personal Pass activated. + * + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_should_display + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_display + */ + public function test_personal_pass_license_activated() { + // Set Personal Pass. + update_option( 'edd_pass_licenses', json_encode( array( + 'license_key' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::PERSONAL_PASS_ID, + 'time_checked' => time() + ) + ) ) ); + + // Simulate that we have a license key. + global $edd_licensed_products; + $edd_licensed_products = array( 'license_key' ); + + $notice = new License_Upgrade_Notice(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'You are using Easy Digital Downloads with a Personal pass.', $notice ); + } + + /** + * Extended Pass activated. + * + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_should_display + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_display + */ + public function test_extended_pass_license_activated() { + // Set Extended Pass. + update_option( 'edd_pass_licenses', json_encode( array( + 'license_key' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::EXTENDED_PASS_ID, + 'time_checked' => time() + ) + ) ) ); + + // Simulate that we have a license key. + global $edd_licensed_products; + $edd_licensed_products = array( 'license_key' ); + + $notice = new License_Upgrade_Notice(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'Grow your business and make more money with affiliate marketing.', $notice ); + } + + /** + * All Access Pass activated. + * + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_should_display + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_display + */ + public function test_all_access_pass_license_activated() { + // Set Pass. + update_option( 'edd_pass_licenses', json_encode( array( + 'license_key' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::ALL_ACCESS_PASS_ID, + 'time_checked' => time() + ) + ) ) ); + + // Simulate that we have a license key. + global $edd_licensed_products; + $edd_licensed_products = array( 'license_key' ); + + $notice = new License_Upgrade_Notice(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'Grow your business and make more money with affiliate marketing.', $notice ); + } + + /** + * Lifetime All Access Pass activated. + * + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_should_display + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_display + */ + public function test_lifetime_all_access_pass_license_activated() { + // Set Pass. + update_option( 'edd_pass_licenses', json_encode( array( + 'license_key' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::ALL_ACCESS_PASS_LIFETIME_ID, + 'time_checked' => time() + ) + ) ) ); + + // Simulate that we have a license key. + global $edd_licensed_products; + $edd_licensed_products = array( 'license_key' ); + + $notice = new License_Upgrade_Notice(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'Grow your business and make more money with affiliate marketing.', $notice ); + } + + /** + * Lifetime All Access Pass *and* Personal Pass activated. + * We should get the notice relevant to the All Access. + * + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_should_display + * @covers \EDD\Admin\Promos\Notices\License_Upgrade_Notice::_display + */ + public function test_aap_wins_over_personal_pass() { + // Set Pass. + update_option( 'edd_pass_licenses', json_encode( array( + 'license_key_1' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::ALL_ACCESS_PASS_LIFETIME_ID, + 'time_checked' => time() + ), + 'license_key_2' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::PERSONAL_PASS_ID, + 'time_checked' => time() + ) + ) ) ); + + // Simulate that we have 2 license keys. + global $edd_licensed_products; + $edd_licensed_products = array( 'license_key_1', 'license_key_2' ); + + $notice = new License_Upgrade_Notice(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'Grow your business and make more money with affiliate marketing.', $notice ); + } + + /** + * Someone running the pro code without a pass license should always see a notice. + * + * @return void + */ + public function test_inactive_pro_sees_notice() { + // skip this test if the inactive pro class doesn't exist + if ( ! class_exists( '\\EDD\\Pro\\Admin\\Promos\\Notices\\InactivePro' ) ) { + $this->markTestSkipped( 'Inactive Pro class does not exist.' ); + } + add_filter( 'edd_is_pro', '__return_true' ); + + $notice = new \EDD\Pro\Admin\Promos\Notices\InactivePro(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'You are using Easy Digital Downloads (Pro) without an active license key.', $notice ); + } + + public function test_inactive_pro_expired_license_sees_notice() { + // skip this test if the inactive pro class doesn't exist + if ( ! class_exists( '\\EDD\\Pro\\Admin\\Promos\\Notices\\InactivePro' ) ) { + $this->markTestSkipped( 'Inactive Pro class does not exist.' ); + } + add_filter( 'edd_is_pro', '__return_true' ); + + LicenseData::get_pro_license( + array( + 'license' => 'expired', + 'expires' => strtotime( '-1 day' ), + ) + ); + $notice = new \EDD\Pro\Admin\Promos\Notices\InactivePro(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'Your license for Easy Digital Downloads (Pro) has expired.', $notice ); + } + + public function test_inactive_pro_expired_license_with_subscription_id_sees_notice() { + // skip this test if the inactive pro class doesn't exist + if ( ! class_exists( '\\EDD\\Pro\\Admin\\Promos\\Notices\\InactivePro' ) ) { + $this->markTestSkipped( 'Inactive Pro class does not exist.' ); + } + add_filter( 'edd_is_pro', '__return_true' ); + + LicenseData::get_pro_license( + array( + 'license' => 'expired', + 'expires' => strtotime( '-1 day' ), + 'subscription_id' => 1234, + ) + ); + $notice = new \EDD\Pro\Admin\Promos\Notices\InactivePro(); + $this->assertTrue( $notice->should_display() ); + $this->assertNoticeContains( 'The last attempt to renew your subscription for Easy Digital Downloads (Pro) failed.', $notice ); + } +} diff --git a/tests/tests-logging.php b/tests/tests-logging.php new file mode 100755 index 00000000000..4ba88944a12 --- /dev/null +++ b/tests/tests-logging.php @@ -0,0 +1,222 @@ +insert_log( array( + 'log_type' => 'gateway_error', + 'post_parent' => 1, + 'post_title' => 'Test Log', + 'post_content' => 'This is a test log inserted from PHPUnit', + ) ); + } + + /** + * @covers ::valid_type() + */ + public function test_valid_log() { + $this->assertTrue( self::$object->valid_type( 'file_download' ) ); + } + + /** + * @covers ::valid_type() + */ + public function test_fake_log() { + $this->assertFalse( self::$object->valid_type( 'foo' ) ); + } + + /** + * @covers ::add() + */ + public function test_add() { + $this->assertNotNull( self::$object->add() ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs() { + $logs = (array) self::$object->get_logs( 1, 'gateway_error' ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::get_connected_logs() + */ + public function test_get_connected_logs() { + $logs = (array) self::$object->get_connected_logs( array( + 'post_parent' => 1, + 'log_type' => 'gateway_error' + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::get_log_count() + */ + public function test_get_log_count() { + $this->assertSame( 1, self::$object->get_log_count( 1, 'gateway_error' ) ); + } + + /** + * @covers ::get_log_count() + */ + public function test_get_log_count_file_downloads() { + $file_download_log = self::$object->insert_log( + array( + 'log_type' => 'file_download', + 'post_parent' => 1, + ), + array( + 'payment_id' => 1234, + ) + ); + $this->assertSame( 1, self::$object->get_log_count( 1, 'file_download' ) ); + } + + /** + * @covers ::delete_logs() + */ + public function test_delete_logs() { + + $this->assertSame( 1, self::$object->get_log_count( 1, 'gateway_error' ) ); + + self::$object->delete_logs( 1 ); + + $this->assertSame( 0, self::$object->get_log_count( 1, 'gateway_error' ) ); + } + + public function test_edd_record_gateway_error() { + + $log_id = edd_record_gateway_error( 'Test gateway error', 'Test gateway error content' ); + $actual_log = edd_get_log( $log_id ); + + $expected = array( + 'object_id' => '0', + 'object_type' => 'gateway_error', + 'user_id' => '0', + 'type' => 'gateway_error', + 'title' => 'Test gateway error', + 'content' => 'Test gateway error content', + ); + + $this->assertSame( $expected['object_id'], $actual_log->object_id ); + $this->assertSame( $expected['object_type'], $actual_log->object_type ); + $this->assertSame( $expected['user_id'], $actual_log->user_id ); + $this->assertSame( $expected['type'], $actual_log->type ); + $this->assertSame( $expected['title'], $actual_log->title ); + $this->assertSame( $expected['content'], $actual_log->content ); + } + + public function test_edd_add_log_with_null_type_and_no_id() { + + $log_id = edd_add_log( + array( + 'object_type' => null, + 'object_id' => 0, + 'title' => 'Test log with null object type and no ID', + ) + ); + + $actual_log = edd_get_log( $log_id ); + + $expected = array( + 'object_id' => '0', + 'object_type' => null, + 'user_id' => '0', + 'type' => '', + 'title' => 'Test log with null object type and no ID', + 'content' => '', + ); + + $this->assertSame( $expected['object_id'], $actual_log->object_id ); + $this->assertSame( $expected['object_type'], $actual_log->object_type ); + $this->assertSame( $expected['user_id'], $actual_log->user_id ); + $this->assertSame( $expected['type'], $actual_log->type ); + $this->assertSame( $expected['title'], $actual_log->title ); + $this->assertSame( $expected['content'], $actual_log->content ); + } + + public function test_edd_add_log_with_null_type_and_an_id() { + + $log_id = edd_add_log( + array( + 'object_type' => null, + 'object_id' => 1, + 'title' => 'Test log with null object type and an ID (should fail)', + ) + ); + + $actual_log = edd_get_log( $log_id ); + + $expected = false; + + $this->assertSame( $expected, $actual_log ); + } + + public function test_edd_add_log_with_empty_type_and_no_id() { + + $log_id = edd_add_log( + array( + 'object_type' => 0, + 'object_id' => 0, + 'title' => 'Test log with empty object type and no ID (should fail)', + ) + ); + + $actual_log = edd_get_log( $log_id ); + + $expected = false; + + $this->assertSame( $expected, $actual_log ); + } + + public function test_edd_add_log_with_empty_type_and_an_id() { + + $log_id = edd_add_log( + array( + 'object_type' => 0, + 'object_id' => 1, + 'title' => 'Test log with empty object type and an ID (should fail)', + ) + ); + + $actual_log = edd_get_log( $log_id ); + + $expected = false; + + $this->assertSame( $expected, $actual_log ); + } + +} diff --git a/tests/tests-migration.php b/tests/tests-migration.php new file mode 100644 index 00000000000..b5978e2f4dc --- /dev/null +++ b/tests/tests-migration.php @@ -0,0 +1,79 @@ +assertFalse( _edd_needs_v3_migration() ); + } + + public function test_edd_needs_v3_migration_final_payment_should_return_true() { + update_option( 'edd_v3_migration_pending', 123456, false ); + + $this->assertTrue( _edd_needs_v3_migration() ); + delete_option( 'edd_v3_migration_pending' ); + } + + public function test_edd_needs_v3_migration_default_tax_rate_should_return_true() { + edd_update_option( 'tax_rate', '.14' ); + + $this->assertTrue( _edd_needs_v3_migration() ); + edd_delete_option( 'tax_rate' ); + } + + public function test_edd_needs_v3_migration_discount_should_return_true() { + $discount_id = wp_insert_post( + array( + 'post_type' => 'edd_discount', + 'post_title' => 'Legacy Discount', + 'post_status' => 'active', + ) + ); + + $this->assertTrue( _edd_needs_v3_migration() ); + wp_delete_post( $discount_id ); + } + + public function test_edd_needs_v3_migration_user_address_should_return_true() { + $this->setExpectedIncorrectUsage( 'add_user_meta()/update_user_meta()' ); + $user_id = wp_create_user( + 'test_migration_user', + 'password' + ); + $address = add_metadata( + 'user', + $user_id, + '_edd_user_address', + array( + 'line1' => 'First address', + 'line2' => 'Line two', + 'city' => 'MyCity', + 'zip' => '12345', + 'country' => 'US', + 'state' => 'AL', + ) + ); + + $this->assertTrue( _edd_needs_v3_migration() ); + wp_delete_user( $user_id ); + } + + public function test_edd_needs_v3_migration_tax_rates_should_return_true() { + $tax_rates = array( + array( + 'country' => 'US', + 'state' => 'AL', + 'rate' => 15, + ), + ); + update_option( 'edd_tax_rates', $tax_rates ); + + $this->assertTrue( _edd_needs_v3_migration() ); + delete_option( 'edd_tax_rates' ); + } +} diff --git a/tests/tests-mime.php b/tests/tests-mime.php new file mode 100755 index 00000000000..abf6746d5d0 --- /dev/null +++ b/tests/tests-mime.php @@ -0,0 +1,24 @@ +assertArrayHasKey( 'zip', $mime ); + $this->assertArrayHasKey( 'epub', $mime ); + $this->assertArrayHasKey( 'mobi', $mime ); + $this->assertArrayHasKey( 'aiff', $mime ); + $this->assertArrayHasKey( 'aif', $mime ); + $this->assertArrayHasKey( 'psd', $mime ); + $this->assertArrayHasKey( 'exe', $mime ); + $this->assertArrayHasKey( 'apk', $mime ); + $this->assertArrayHasKey( 'msi', $mime ); + } +} diff --git a/tests/tests-misc.php b/tests/tests-misc.php new file mode 100755 index 00000000000..5372db40289 --- /dev/null +++ b/tests/tests-misc.php @@ -0,0 +1,523 @@ +assertFalse( edd_is_test_mode() ); + } + + public function test_guest_checkout() { + $this->assertFalse( edd_no_guest_checkout() ); + } + + public function test_logged_in_only() { + $this->assertFalse( edd_logged_in_only() ); + } + + public function test_straight_to_checkout() { + $this->assertFalse( edd_straight_to_checkout() ); + } + + public function test_no_redownload() { + $this->assertFalse( edd_no_redownload() ); + } + + public function test_is_cc_verify_enabled() { + $this->assertTrue( edd_is_cc_verify_enabled() ); + } + + public function test_is_odd() { + $this->assertTrue( edd_is_odd( 3 ) ); + $this->assertFalse( edd_is_odd( 4 ) ); + } + + public function test_get_file_extension() { + $this->assertEquals( 'php', edd_get_file_extension( 'file.php' ) ); + } + + public function test_get_file_extension_with_query_string() { + $this->assertEquals( 'pdf', edd_get_file_extension( 'file.pdf?test=1' ) ); + } + + public function test_string_is_image_url() { + $this->assertTrue( edd_string_is_image_url( 'jpg' ) ); + $this->assertTrue( edd_string_is_image_url( 'webp' ) ); + $this->assertFalse( edd_string_is_image_url( 'php' ) ); + } + + public function test_get_ip() { + $this->assertEquals( '127.0.0.1', edd_get_ip() ); + + $_SERVER['REMOTE_ADDR'] = '172.217.6.46'; + $this->assertEquals( '172.217.6.46', edd_get_ip() ); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + } + + public function test_get_ip_reverse_proxies() { + $_SERVER['HTTP_X_FORWARDED_FOR'] = '123.123.123.123, 10.0.0.2'; + $this->assertEquals( '123.123.123.123', edd_get_ip() ); + unset($_SERVER['HTTP_X_FORWARDED_FOR']); + } + + public function test_get_ip_reverse_proxy() { + $_SERVER['HTTP_X_FORWARDED_FOR'] = '123.123.123.123'; + $this->assertEquals( '123.123.123.123', edd_get_ip() ); + unset($_SERVER['HTTP_X_FORWARDED_FOR']); + } + + + public function test_get_currencies() { + $expected = array( + 'USD' => __( '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' ), + ); + + $this->assertEquals( $expected, edd_get_currencies() ); + + } + + public function test_month_num_to_name() { + $this->assertEquals( 'Jan', edd_month_num_to_name( 1 ) ); + } + + /** + * @covers ::edd_month_num_to_name() + */ + public function test_month_num_to_long_name() { + $this->assertEquals( 'January', edd_month_num_to_name( 1, true ) ); + } + + public function test_get_php_arg_separator_output() { + $this->assertEquals( '&', edd_get_php_arg_separator_output() ); + } + + public function test_let_to_num() { + $this->assertEquals( 0, edd_let_to_num( WP_MEMORY_LIMIT ) / ( 1024*1024 ) ); + } + + /** + * @covers ::edd_get_symlink_dir + */ + public function test_get_symlink_url() { + $this->assertEquals( 'http://example.org/wp-content/uploads/edd/symlinks', edd_get_symlink_url() ); + } + + public function test_use_skus() { + $this->assertFalse( edd_use_skus() ); + } + + public function test_edd_is_host() { + $this->assertFalse( edd_is_host( 'wpengine' ) ); + $this->assertFalse( edd_is_host( 'wp engine' ) ); + $this->assertFalse( edd_is_host( 'WP Engine' ) ); + $this->assertFalse( edd_is_host( 'WPEngine' ) ); + + define( 'WPE_APIKEY', 'testkey' ); + + $this->assertTrue( edd_is_host( 'wpengine' ) ); + $this->assertTrue( edd_is_host( 'wp engine' ) ); + $this->assertTrue( edd_is_host( 'WP Engine' ) ); + $this->assertTrue( edd_is_host( 'WPEngine' ) ); + } + + public function test_edd_update_option(){ + $key = 'some-setting'; + $value = 'some-value'; + $isset = edd_get_option( $key, false ); + + // The option shouldn't exist + $this->assertFalse( $isset ); + + $updated = edd_update_option( $key, $value ); + + // The option should have successfully updated + $this->assertTrue( $updated ); + + // The option retrieve should be equal to the one we set + $this->assertEquals( $value, edd_get_option( $key, false ) ); + + $key = 'some-setting2'; + $value = null; + $isset = edd_get_option( $key, false ); + + // The option shouldn't exist + $this->assertFalse( $isset ); + + $updated = edd_update_option( $key, $value ); + + // The option should return false due to the null value + $this->assertFalse( $updated ); + + // The option retrieve should be false since it doesn't exist + $this->assertFalse( edd_get_option( $key, false ) ); + + } + + public function test_add_cache_busting() { + add_filter( 'edd_is_caching_plugin_active', '__return_true' ); + $this->assertEquals( 'http://example.org/?nocache=true', edd_add_cache_busting( home_url( '/') ) ); + remove_filter( 'edd_is_caching_plugin_active', '__return_true' ); + $this->assertEquals( 'http://example.org/', edd_add_cache_busting( home_url( '/' ) ) ); + } + + /** + * @covers ::edd_get_current_page_url() + */ + public function test_get_current_page_url_if_home_should_return_home_url() { + $this->go_to( home_url( '/' ) ); + $this->assertEquals( 'http://example.org/', edd_get_current_page_url() ); + } + + /** + * @covers ::edd_get_current_page_url() + */ + public function test_get_current_page_url_if_a_download_page_should_return_that_url() { + $this->go_to( get_permalink( self::$download->ID ) ); + $this->assertEquals( 'http://' . WP_TESTS_DOMAIN . '/?download=test-download-product', edd_get_current_page_url() ); + } + + /** + * @covers ::edd_get_current_page_url() + */ + public function test_get_current_page_url_if_no_caching_should_return_url_with_nocache_true() { + add_filter( 'edd_is_caching_plugin_active', '__return_true' ); + + $this->go_to( get_permalink( self::$download->ID ) ); + + $this->assertEquals( 'http://' . WP_TESTS_DOMAIN . '/?download=test-download-product&nocache=true', edd_get_current_page_url( true ) ); + + remove_filter( 'edd_is_caching_plugin_active', '__return_true' ); + } + + /** + * @covers ::edd_get_current_page_url() + */ + public function test_get_current_page_url_if_no_cache_checkout_then_current_url_should_match() { + global $edd_options; + + add_filter( 'edd_is_caching_plugin_active', '__return_true' ); + + $edd_options['no_cache_checkout'] = true; + + $this->go_to( get_permalink( $edd_options['purchase_page'] ) ); + + $this->assertEquals( edd_get_checkout_uri(), edd_get_current_page_url( true ) ); + + remove_filter( 'edd_is_caching_plugin_active', '__return_true' ); + } + + public function test_cart_url_formats() { + global $edd_options; + $post = Helpers\EDD_Helper_Download::create_simple_download(); + + edd_add_to_cart( $post->ID ); + + $this->assertTrue( edd_item_in_cart( $post->ID ) ); + + $item_position = edd_get_item_position_in_cart( $post->ID ); + + // Go to checkout + $this->go_to( edd_get_checkout_uri() ); + + add_filter( 'edd_is_caching_plugin_active', '__return_true' ); + + $remove_url = edd_remove_item_url( $item_position ); + + $this->assertStringContainsString( 'page_id=' . $edd_options['purchase_page'], $remove_url ); + $this->assertStringContainsString( 'edd_action=remove', $remove_url ); + $this->assertStringContainsString( 'nocache=true', $remove_url ); + $this->assertStringContainsString( 'cart_item=' . $item_position, $remove_url ); + + remove_filter( 'edd_is_caching_plugin_active', '__return_true' ); + unset( $edd_options['no_cache_checkout'] ); + $remove_url = edd_remove_item_url( $item_position ); + + $this->assertStringContainsString( 'page_id=' . $edd_options['purchase_page'], $remove_url ); + $this->assertStringContainsString( 'edd_action=remove', $remove_url ); + $this->assertStringContainsString( 'cart_item=' . $item_position, $remove_url ); + $this->assertStringNotContainsString( 'nocache=true', $remove_url ); + + // Go home and test again + $this->go_to( home_url( '/' ) ); + + add_filter( 'edd_is_caching_plugin_active', '__return_true' ); + + $expected_url = 'http://example.org/?cart_item=' . $item_position . '&edd_action=remove&nocache=true'; + $remove_url = edd_remove_item_url( $item_position ); + + $this->assertStringNotContainsString( 'page_id=', $remove_url ); + $this->assertStringContainsString( 'edd_action=remove', $remove_url ); + $this->assertStringContainsString( 'cart_item=' . $item_position, $remove_url ); + $this->assertStringContainsString( 'nocache=true', $remove_url ); + + remove_filter( 'edd_is_caching_plugin_active', '__return_true' ); + + $remove_url = edd_remove_item_url( $item_position ); + + $this->assertStringNotContainsString( 'page_id=', $remove_url ); + $this->assertStringContainsString( 'edd_action=remove', $remove_url ); + $this->assertStringContainsString( 'cart_item=' . $item_position, $remove_url ); + $this->assertStringNotContainsString( 'nocache=true', $remove_url ); + + // Go home and test again + $this->go_to( home_url( '/' ) ); + + add_filter( 'edd_is_caching_plugin_active', '__return_true' ); + + $expected_url = 'http://example.org/?cart_item=' . $item_position . '&edd_action=remove&nocache=true'; + $remove_url = edd_remove_item_url( $item_position ); + + $this->assertEquals( $expected_url, $remove_url ); + remove_filter( 'edd_is_caching_plugin_active', '__return_true' ); + + $remove_url = edd_remove_item_url( $item_position ); + $expected_url = 'http://example.org/?cart_item=' . $item_position . '&edd_action=remove'; + + Helpers\EDD_Helper_Download::delete_download( $post->ID ); + } + + public function test_array_convert() { + $customer1_id = edd_add_customer( array( 'email' => 'test10@example.com' ) ); + + // Test sending a single object in + $customer_object = new \EDD_Customer( $customer1_id ); + $customer_array = edd_object_to_array( $customer_object ); + $this->assertIsArray( $customer_array ); + $this->assertEquals( $customer_object->id, $customer_array['id'] ); + $this->assertEquals( $customer_object->email, $customer_array['email'] ); + $this->assertEquals( $customer_object->purchase_count, $customer_array['purchase_count'] ); + + // Negative tests (no alterations should occur) + $this->assertEquals( 'string', edd_object_to_array( 'string' ) ); + $this->assertEquals( array( 'foo', 'bar', 'baz' ), edd_object_to_array( array( 'foo', 'bar', 'baz' ) ) ); + + // Test sending in an array of objects + $customers = edd_get_customers(); + $converted = edd_object_to_array( $customers ); + $this->assertIsArray( $converted[0] ); + + // Test payments + $payment_1 = Helpers\EDD_Helper_Payment::create_simple_payment(); + $payment_2 = Helpers\EDD_Helper_Payment::create_simple_payment(); + + $payment_1_obj = new \EDD_Payment( $payment_1 ); + $payment_2_obj = new \EDD_Payment( $payment_2 ); + + // Test a single convert + $payment_1_array = edd_object_to_array( $payment_1_obj ); + $this->assertIsArray( $payment_1_array ); + $this->assertEquals( $payment_1_obj->ID, $payment_1_array['ID'] ); + + $payments = array( + $payment_1_obj, + $payment_2_obj, + ); + + $payments_array = edd_object_to_array( $payments ); + $this->assertIsArray( $payments_array[0] ); + $this->assertEquals( 2, count( $payments_array ) ); + } + + // Test getting currency symols: + function test_gbp_symbol() { + $this->assertEquals( '£', edd_currency_symbol( 'GBP' ) ); + } + + function test_brl_symbol() { + $this->assertEquals( 'R$', edd_currency_symbol( 'BRL' ) ); + } + + function test_us_dollar_symbol() { + $this->assertEquals( '$', edd_currency_symbol( 'USD' ) ); + } + + function test_au_dollar_symbol() { + $this->assertEquals( '$', edd_currency_symbol( 'AUD' ) ); + } + + function test_nz_dollar_symbol() { + $this->assertEquals( '$', edd_currency_symbol( 'NZD' ) ); + } + + function test_ca_dollar_symbol() { + $this->assertEquals( '$', edd_currency_symbol( 'CAD' ) ); + } + + function test_hk_dollar_symbol() { + $this->assertEquals( '$', edd_currency_symbol( 'HKD' ) ); + } + + function test_mx_dollar_symbol() { + $this->assertEquals( '$', edd_currency_symbol( 'MXN' ) ); + } + + function test_sg_dollar_symbol() { + $this->assertEquals( '$', edd_currency_symbol( 'SGD' ) ); + } + + function test_yen_symbol() { + $this->assertEquals( '¥', edd_currency_symbol( 'JPY' ) ); + } + + function test_aoa_symbol() { + $this->assertEquals( 'Kz', edd_currency_symbol( 'AOA' ) ); + } + + function test_default_symbol() { + $this->assertEquals( 'CZK', edd_currency_symbol( 'CZK' ) ); + } + + function test_edd_delete_option() { + edd_update_option( 'test_setting', 'testing' ); + edd_delete_option( 'test_setting' ); + + $this->assertFalse( edd_get_option( 'test_setting' ) ); + } + + function test_should_allow_file_download_edd_uploaded_file_url() { + $file_details = array ( 'scheme' => 'https', 'host' => site_url(), 'path' => '/wp-content/uploads/edd/2019/04/test-file.jpg' ); + $schemas = array ( 0 => 'http', 1 => 'https' ); + $requested_file = trailingslashit( site_url() ) . 'wp-content/uploads/edd/2019/04/test-file.jpg'; + + $this->assertTrue( edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) ); + } + + function test_should_allow_file_download_uploaded_file_in_content_url() { + $file_details = array ( 'scheme' => 'https', 'host' => site_url(), 'path' => '/wp-content/my-files/test-file.jpg' ); + $schemas = array ( 0 => 'http', 1 => 'https' ); + $requested_file = trailingslashit( site_url() ) . '/wp-content/my-files/test-file.jpg'; + + $this->assertTrue( edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) ); + } + + function test_should_allow_file_download_uploaded_file_in_content_absolute_in_content() { + $this->write_test_file( trailingslashit( WP_CONTENT_DIR ) . 'test-file.jpg' ); + $file_details = array ( 'path' => trailingslashit( WP_CONTENT_DIR ) . 'test-file.jpg' ); + $schemas = array ( 0 => 'http', 1 => 'https' ); + $requested_file = trailingslashit( WP_CONTENT_DIR ) . 'test-file.jpg'; + + $this->assertTrue( edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) ); + $this->delete_test_file( trailingslashit( WP_CONTENT_DIR ) . 'test-file.jpg' ); + } + + function test_should_allow_file_download_uploaded_file_in_content_absolute_outside_of_content() { + $this->write_test_file( trailingslashit( ABSPATH ) . 'test-file.jpg' ); + $file_details = array ( 'path' => trailingslashit( ABSPATH ) . 'test-file.jpg' ); + $schemas = array ( 0 => 'http', 1 => 'https' ); + $requested_file = trailingslashit( ABSPATH ) . 'test-file.jpg'; + + $this->assertFalse( edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) ); + $this->delete_test_file( trailingslashit( ABSPATH ) . 'test-file.jpg' ); + } + + function test_should_allow_file_download_uploaded_file_in_content_url_on_windows_WAMP() { + $file_details = array ( 'scheme' => 'https', 'host' => site_url(), 'path' => 'E:\wamp\www\site\wp/wp-content/my-files/test-file.jpg' ); + $schemas = array ( 0 => 'http', 1 => 'https' ); + $requested_file = trailingslashit( site_url() ) . '/wp-content/my-files/test-file.jpg'; + + $this->assertTrue( edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) ); + } + + function test_should_allow_file_download_uploaded_file_in_content_absolute_outside_of_content_on_windows_WAMP() { + $file_details = array ( 'path' => 'E:\wamp\www\site\wp/test-file.jpg' ); + $schemas = array ( 0 => 'http', 1 => 'https' ); + $requested_file = 'E:\wamp\www\site\wp/test-file.jpg'; + + $this->assertFalse( edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) ); + } + + function test_should_allow_file_download_uploaded_file_in_content_url_on_windows_IIS() { + $file_details = array ( 'scheme' => 'https', 'host' => site_url(), 'path' => 'C:\inetpub\wwwroot\mysite/wp-content/my-files/test-file.jpg' ); + $schemas = array ( 0 => 'http', 1 => 'https' ); + $requested_file = trailingslashit( site_url() ) . '/wp-content/my-files/test-file.jpg'; + + $this->assertTrue( edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) ); + } + + function test_should_allow_file_download_uploaded_file_in_content_absolute_outside_of_content_on_windows_IIS() { + $file_details = array ( 'path' => 'C:\inetpub\wwwroot\mysite/test-file.jpg' ); + $schemas = array ( 0 => 'http', 1 => 'https' ); + $requested_file = 'C:\inetpub\wwwroot\mysite/test-file.jpg'; + + $this->assertFalse( edd_local_file_location_is_allowed( $file_details, $schemas, $requested_file ) ); + } + + function test_is_countable_defined() { + $this->assertTrue( function_exists( 'is_countable' ) ); + } + + function test_is_iterable_defined() { + $this->assertTrue( function_exists( 'is_iterable' ) ); + } + + function test_postal_codes_SE_leading_s() { + $this->assertTrue( edd_purchase_form_validate_cc_zip( 's-12345', 'SE' ) ); + } + + function test_postal_codes_SE_leading_capital_s() { + $this->assertTrue( edd_purchase_form_validate_cc_zip( 'S-12345', 'SE' ) ); + } + + function test_postal_codes_SE_numeric() { + $this->assertTrue( edd_purchase_form_validate_cc_zip( '12345', 'SE' ) ); + } + + private function write_test_file( $full_file_path ) { + $file = FileSystem::fopen( $full_file_path, "w" ); + fwrite( $file,"" ); + fclose( $file ); + } + + private function delete_test_file( $full_file_path ) { + unlink( $full_file_path ); + } +} diff --git a/tests/tests-pass-manager.php b/tests/tests-pass-manager.php new file mode 100644 index 00000000000..3f881e736ef --- /dev/null +++ b/tests/tests-pass-manager.php @@ -0,0 +1,160 @@ +assertFalse( $manager->has_pass() ); + } + + /** + * @covers \EDD\Admin\Pass_Manager::pass_compare + */ + public function test_all_access_is_higher_than_personal() { + $this->assertTrue( + \EDD\Admin\Pass_Manager::pass_compare( + \EDD\Admin\Pass_Manager::ALL_ACCESS_PASS_ID, + \EDD\Admin\Pass_Manager::PERSONAL_PASS_ID, + '>' + ) + ); + } + + /** + * @covers \EDD\Admin\Pass_Manager::pass_compare + */ + public function test_personal_pass_equals() { + $this->assertTrue( + \EDD\Admin\Pass_Manager::pass_compare( + 1245715, + \EDD\Admin\Pass_Manager::PERSONAL_PASS_ID, + '=' + ) + ); + } + + public function test_invalid_pass_returns_false() { + $this->assertFalse( + \EDD\Admin\Pass_Manager::pass_compare( + 1234856, + \EDD\Admin\Pass_Manager::PERSONAL_PASS_ID, + '=' + ) + ); + } + + /** + * If you have both a Personal and Professional pass activated, the Professional should be highest. + * + * @covers \EDD\Admin\Pass_Manager::set_highest_pass_data() + */ + public function test_professional_is_highest_pass() { + $passes = array( + 'license_1' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::PERSONAL_PASS_ID, + 'time_checked' => time() + ), + 'license_2' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::PROFESSIONAL_PASS_ID, + 'time_checked' => time() + ), + ); + + update_option( 'edd_pass_licenses', json_encode( $passes ) ); + + $manager = new \EDD\Admin\Pass_Manager(); + $this->assertSame( \EDD\Admin\Pass_Manager::PROFESSIONAL_PASS_ID, $manager->highest_pass_id ); + } + + /** + * If you have a pass entered, but it was last verified more than 2 months ago (1 year ago + * in this case), then it should not be accepted as a valid pass. + * + * @covers \EDD\Admin\Pass_Manager::set_highest_pass_data() + */ + public function test_no_pass_id_if_pass_outside_check_window() { + $passes = array( + 'license_1' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::PERSONAL_PASS_ID, + 'time_checked' => strtotime( '-1 year' ) + ) + ); + + update_option( 'edd_pass_licenses', json_encode( $passes ) ); + $manager = new \EDD\Admin\Pass_Manager(); + + $this->assertFalse( $manager->has_pass() ); + } + + /** + * @covers \EDD\Admin\Pass_Manager::isFree + */ + public function test_site_with_no_licenses() { + $passManager = new \EDD\Admin\Pass_Manager(); + + $this->assertTrue( $passManager->isFree() ); + $this->assertFalse( $passManager->hasPersonalPass() ); + $this->assertFalse( $passManager->hasExtendedPass() ); + $this->assertFalse( $passManager->hasProfessionalPass() ); + $this->assertFalse( $passManager->hasAllAccessPass() ); + $this->assertFalse( $passManager->has_pass() ); + } + + /** + * @covers \EDD\Admin\Pass_Manager::hasPersonalPass + */ + public function test_site_with_personal_pass() { + $passes = array( + 'license_1' => array( + 'pass_id' => \EDD\Admin\Pass_Manager::PERSONAL_PASS_ID, + 'time_checked' => time() + ), + ); + + update_option( 'edd_pass_licenses', json_encode( $passes ) ); + + global $edd_licensed_products; + $edd_licensed_products[] = 'product'; + + $passManager = new \EDD\Admin\Pass_Manager(); + + $this->assertFalse( $passManager->isFree() ); + $this->assertTrue( $passManager->hasPersonalPass() ); + $this->assertTrue( $passManager->has_pass() ); + } + +} diff --git a/tests/tests-plugin-compatibility.php b/tests/tests-plugin-compatibility.php new file mode 100644 index 00000000000..5ca2dcbe28a --- /dev/null +++ b/tests/tests-plugin-compatibility.php @@ -0,0 +1,173 @@ +assertNotFalse( has_action( 'load-edit.php', 'edd_remove_post_types_order' ) ); + $this->assertNotFalse( has_action( 'template_redirect', 'edd_disable_jetpack_og_on_checkout' ) ); + $this->assertNotFalse( has_filter( 'edd_settings_misc', 'edd_append_no_cache_param' ) ); + $this->assertNotFalse( has_filter( 'edd_downloads_content', 'edd_qtranslate_content' ) ); + $this->assertNotFalse( has_filter( 'edd_downloads_excerpt', 'edd_qtranslate_content' ) ); + $this->assertNotFalse( has_action( 'template_redirect', 'edd_disable_woo_ssl_on_checkout' ) ); + $this->assertNotFalse( has_action( 'edd_email_send_before', 'edd_disable_mandrill_nl2br' ) ); + $this->assertNotFalse( has_action( 'template_redirect', 'edd_disable_404_redirected_redirect' ) ); + + } + + /** + * Test that the 'CPTOrderPosts' filter is removed. + * + * @since 2.3 + */ + public function test_remove_post_types_order() { + + edd_remove_post_types_order(); + $this->assertFalse( has_filter( 'posts_orderby', 'CPTOrderPosts' ) ); + + } + + /** + * Test that the JetPack og tags are removed. + * + * @since 2.3 + */ + public function test_disable_jetpack_og_on_checkout() { + + $this->go_to( get_permalink( edd_get_option( 'purchase_page' ) ) ); + edd_disable_jetpack_og_on_checkout(); + $this->assertFalse( has_action( 'wp_head', 'jetpack_og_tags' ) ); + + } + + /** + * Test that the edd_is_caching_plugin_active() return false when no caching is installed. + * + * @since 2.3 + */ + public function test_is_caching_plugin_active_false() { + + $this->assertFalse( edd_is_caching_plugin_active() ); + + } + + /** + * Test that edd_is_chaching_plugin_active() return true when W3TC is active. + * + * @since 2.3 + */ + public function test_is_caching_plugin_active_true() { + + define( 'W3TC', true ); + $this->assertTrue( edd_is_caching_plugin_active() ); + + } + + /** + * Test that a extra setting is added when W3TC is activated. + * + * @since 2.3 + */ + public function test_append_no_cache_param() { + + $settings = edd_append_no_cache_param( $settings = array() ); + + $this->assertEquals( $settings, array( array( + 'id' => 'no_cache_checkout', + 'name' => __('No Caching on Checkout?','easy-digital-downloads' ), + 'desc' => __('Check this box in order to append a ?nocache parameter to the checkout URL to prevent caching plugins from caching the page.','easy-digital-downloads' ), + 'type' => 'checkbox' + ) ) ); + + } + + /** + * Test the qTranslate function. + * + * @since 2.3 + */ + public function test_qtranslate_content() { + + define( 'QT_LANGUAGE', true ); + $content = \edd_qtranslate_content( $content = 'This is some test content' ); + $this->assertEquals( $content, 'This is some test content' ); + + } + + /** + * Test that the Woo SSL action is removed from the template_redirect hook. + * + * @since 2.3 + */ + public function test_disable_woo_ssl_on_checkout() { + + $this->go_to( get_permalink( edd_get_option( 'purchase_page' ) ) ); + add_filter( 'edd_is_ssl_enforced', '__return_true' ); + + edd_disable_woo_ssl_on_checkout(); + $this->assertFalse( has_action( 'template_redirect', array( 'WC_HTTPS', 'unforce_https_template_redirect' ) ) ); + + } + + /** + * Test the Mandrill compatibility function. + * + * @since 2.3 + */ + public function test_disable_mandrill_nl2br() { + + edd_disable_mandrill_nl2br(); + $this->assertNotFalse( has_action( 'mandrill_nl2br', '__return_false' ) ); + + } + + /** + * Test that the edd_disable_404_redirected_redirect() functions returns when WBZ404_VERSION is not defined. + * + * @since 2.3 + */ + public function test_disable_404_redirected_redirect_return() { + + $this->assertNull( edd_disable_404_redirected_redirect() ); + + } + + /** + * Test the edd_disable_404_redirected_redirect function. + * + * @since 2.3 + */ + public function test_disable_404_redirected_redirect() { + + $this->go_to( get_permalink( edd_get_option( 'success_page' ) ) ); + define( 'WBZ404_VERSION', '1.3.2' ); + edd_disable_404_redirected_redirect(); + + $this->assertFalse( has_action( 'template_redirect', 'wbz404_process404' ) ); + + } + + public function test_say_what_aliases() { + + global $wp_filter; + $this->assertarrayHasKey( 'edd_say_what_domain_aliases', $wp_filter['say_what_domain_aliases'][10] ); + + $say_what_aliases = apply_filters( 'say_what_domain_aliases', array() ); + $this->assertarrayHasKey( 'easy-digital-downloads', $say_what_aliases ); + $this->assertTrue( in_array( 'edd', $say_what_aliases['easy-digital-downloads'] ) ); + + } + + +} diff --git a/tests/tests-polyfills.php b/tests/tests-polyfills.php new file mode 100644 index 00000000000..836b39cf1d6 --- /dev/null +++ b/tests/tests-polyfills.php @@ -0,0 +1,34 @@ +assertTrue( function_exists( 'wp_readonly' ) ); + } + + public function test_wp_readonly_true_returns_readonly_string() { + $this->assertStringContainsString( 'readonly', wp_readonly( true, true, false ) ); + } + + public function test_cal_days_in_month_exists() { + $this->assertTrue( function_exists( 'cal_days_in_month' ) ); + } + + public function test_cal_days_in_month_january_2021() { + $this->assertEquals( 31, cal_days_in_month( CAL_GREGORIAN, 1, 2021 ) ); + } + + public function test_getallheaders_exists() { + $this->assertTrue( function_exists( 'getallheaders' ) ); + } +} diff --git a/tests/tests-post-type-labels.php b/tests/tests-post-type-labels.php new file mode 100755 index 00000000000..10601a7f6a1 --- /dev/null +++ b/tests/tests-post-type-labels.php @@ -0,0 +1,59 @@ +assertArrayHasKey( 'singular', $out ); + $this->assertArrayHasKey( 'plural', $out ); + + $this->assertEquals( 'Download', $out['singular'] ); + $this->assertEquals( 'Downloads', $out['plural'] ); + } + + public function test_singular_label() { + $this->assertEquals( 'Download', edd_get_label_singular() ); + $this->assertEquals( 'download', edd_get_label_singular( true ) ); + } + + public function test_plural_label() { + $this->assertEquals( 'Downloads', edd_get_label_plural() ); + $this->assertEquals( 'downloads', edd_get_label_plural( true ) ); + } + + public function test_taxonomy_labels() { + + $category_labels = edd_get_taxonomy_labels(); + $this->assertIsArray( $category_labels ); + $this->assertArrayHasKey( 'name', $category_labels ); + $this->assertArrayHasKey( 'singular_name', $category_labels ); + $this->assertTrue( in_array( 'Download Category', $category_labels ) ); + $this->assertTrue( in_array( 'Download Categories', $category_labels ) ); + // Negative test for our change to exclude singular post type label in #3212 + $this->assertTrue( in_array( 'Categories', $category_labels ) ); + + $this->assertIsArray( $category_labels ); + $this->assertArrayHasKey( 'name', $category_labels ); + $this->assertArrayHasKey( 'singular_name', $category_labels ); + $this->assertTrue( in_array( 'Download Category', $category_labels ) ); + $this->assertTrue( in_array( 'Download Categories', $category_labels ) ); + // Negative test for our change to exclude singular post type label in #3212 + $this->assertTrue( in_array( 'Categories', $category_labels ) ); + + $tag_labels = edd_get_taxonomy_labels( 'download_tag' ); + $this->assertIsArray( $tag_labels ); + $this->assertArrayHasKey( 'name', $tag_labels ); + $this->assertArrayHasKey( 'singular_name', $tag_labels ); + $this->assertTrue( in_array( 'Download Tag', $tag_labels ) ); + $this->assertTrue( in_array( 'Download Tags', $tag_labels ) ); + // Negative test for our change to exclude singular post type label in #3212 + $this->assertTrue( in_array( 'Tags', $tag_labels ) ); + + } +} diff --git a/tests/tests-post-types.php b/tests/tests-post-types.php new file mode 100755 index 00000000000..5d00b1da46d --- /dev/null +++ b/tests/tests-post-types.php @@ -0,0 +1,41 @@ +assertArrayHasKey( 'download', $wp_post_types ); + } + + public function test_downloads_post_type_labels() { + global $wp_post_types; + $this->assertEquals( 'Downloads', $wp_post_types['download']->labels->name ); + $this->assertEquals( 'Download', $wp_post_types['download']->labels->singular_name ); + $this->assertEquals( 'Add New', $wp_post_types['download']->labels->add_new ); + $this->assertEquals( 'Add New Download', $wp_post_types['download']->labels->add_new_item ); + $this->assertEquals( 'Edit Download', $wp_post_types['download']->labels->edit_item ); + $this->assertEquals( 'View Download', $wp_post_types['download']->labels->view_item ); + $this->assertEquals( 'Search Downloads', $wp_post_types['download']->labels->search_items ); + $this->assertEquals( 'No Downloads found', $wp_post_types['download']->labels->not_found ); + $this->assertEquals( 'No Downloads found in Trash', $wp_post_types['download']->labels->not_found_in_trash ); + $this->assertEquals( 'Downloads', $wp_post_types['download']->labels->all_items ); + $this->assertEquals( 'Downloads', $wp_post_types['download']->labels->menu_name ); + $this->assertEquals( 'Download', $wp_post_types['download']->labels->name_admin_bar ); + $this->assertEquals( 1, $wp_post_types['download']->publicly_queryable ); + $this->assertEquals( 'product', $wp_post_types['download']->capability_type ); + $this->assertEquals( 1, $wp_post_types['download']->map_meta_cap ); + $this->assertEquals( 'downloads', $wp_post_types['download']->rewrite['slug'] ); + $this->assertEquals( 1, $wp_post_types['download']->has_archive ); + $this->assertEquals( 'download', $wp_post_types['download']->query_var ); + $this->assertEquals( 'Downloads', $wp_post_types['download']->label ); + } +} diff --git a/tests/tests-query-filters.php b/tests/tests-query-filters.php new file mode 100644 index 00000000000..ba9f2b5d267 --- /dev/null +++ b/tests/tests-query-filters.php @@ -0,0 +1,263 @@ +assertNotFalse( has_action( 'template_redirect', 'edd_block_attachments' ) ); + + } + + /** + * Test that the function bails when not on a attachment page. + * + * @since 2.2.4 + */ + public function test_edd_block_attachments_no_attachment_bail() { + + // Nothing to prepare, already not on a 'is_attachment' page + + $this->assertNull( edd_block_attachments() ); + + } + + /** + * Test that the edd_block_attachments() function bails when the file has no parent. + * + * @since 2.2.4 + */ + public function test_edd_block_attachments_no_parent_bail() { + + // Prepare test + $filename = EDD_PLUGIN_DIR . 'assets/images/loading.gif'; + $filetype = wp_check_filetype( basename( $filename ), null ); + $wp_upload_dir = wp_upload_dir(); + + $attachment = array( + 'guid' => $wp_upload_dir['url'] . '/' . basename( $filename ), + 'post_mime_type' => $filetype['type'], + 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ), + 'post_content' => '', + 'post_status' => 'inherit' + ); + $attach_id = wp_insert_attachment( $attachment, basename( $filename ), 0 ); + require_once( ABSPATH . 'wp-admin/includes/image.php' ); + $attach_data = wp_generate_attachment_metadata( $attach_id, $filename ); + wp_update_attachment_metadata( $attach_id, $attach_data ); + + $this->go_to( get_permalink( $attach_id ) ); + + $this->assertNull( edd_block_attachments() ); + + // Reset to origin + wp_delete_attachment( $attach_id, true ); + $this->go_to( '' ); + + } + + /** + * Test that the edd_block_attachments() function bails when the parent is not a download. + * + * @since 2.2.4 + */ + public function test_edd_block_attachments_no_download_bail() { + + // Prepare test + $parent_post_id = $this->factory->post->create( array( + 'post_title' => 'Hello World', + 'post_name' => 'hello-world', + 'post_type' => 'post', + 'post_status' => 'publish' + ) ); + + $filename = EDD_PLUGIN_DIR . 'assets/images/loading.gif'; + $parent_post_id = $parent_post_id; + $filetype = wp_check_filetype( basename( $filename ), null ); + $wp_upload_dir = wp_upload_dir(); + + $attachment = array( + 'guid' => $wp_upload_dir['url'] . '/' . basename( $filename ), + 'post_mime_type' => $filetype['type'], + 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ), + 'post_content' => '', + 'post_status' => 'inherit' + ); + $attach_id = wp_insert_attachment( $attachment, basename( $filename ), $parent_post_id ); + require_once( ABSPATH . 'wp-admin/includes/image.php' ); + $attach_data = wp_generate_attachment_metadata( $attach_id, $filename ); + wp_update_attachment_metadata( $attach_id, $attach_data ); + + $this->go_to( get_permalink( $attach_id ) ); + + $this->assertNull( edd_block_attachments() ); + + // Reset to origin + wp_delete_post( $parent_post_id, true ); + wp_delete_attachment( $attach_id, true ); + $this->go_to( '' ); + + } + + /** + * Test that the edd_block_attachments() function will retrun when the content is not restricted. + * + * @since 2.2.4 + */ + public function test_edd_block_attachments_not_restricted_bail() { + + // Prepare test + $parent_post_id = $this->factory->post->create( array( + 'post_title' => 'Test Download Product', + 'post_name' => 'test-download-product', + 'post_type' => 'download', + 'post_status' => 'publish' + ) ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array(), + 'edd_download_files' => array(), + '_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, + 'edd_sku' => 'sku_0012' + ); + foreach( $meta as $key => $value ) { + update_post_meta( $parent_post_id, $key, $value ); + } + + $filename = EDD_PLUGIN_DIR . 'assets/images/loading.gif'; + $parent_post_id = $parent_post_id; + $filetype = wp_check_filetype( basename( $filename ), null ); + $wp_upload_dir = wp_upload_dir(); + + $attachment = array( + 'guid' => $wp_upload_dir['url'] . '/' . basename( $filename ), + 'post_mime_type' => $filetype['type'], + 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ), + 'post_content' => '', + 'post_status' => 'inherit' + ); + $attach_id = wp_insert_attachment( $attachment, $filename, $parent_post_id ); + require_once( ABSPATH . 'wp-admin/includes/image.php' ); + $attach_data = wp_generate_attachment_metadata( $attach_id, $filename ); + wp_update_attachment_metadata( $attach_id, $attach_data ); + + $this->go_to( get_permalink( $attach_id ) ); + + $this->assertNull( edd_block_attachments() ); + + // Reset to origin + wp_delete_post( $parent_post_id, true ); + wp_delete_attachment( $attach_id, true ); + $this->go_to( '' ); + + } + + /** + * Test that the edd_block_attachments() function dies when the file is restricted. + * + * @since 2.2.4 + */ + public function test_edd_block_attachments_die() { + + // Prepare test + $parent_post_id = $this->factory->post->create( array( + 'post_title' => 'Test Download Product', + 'post_name' => 'test-download-product', + 'post_type' => 'download', + 'post_status' => 'publish' + ) ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array(), + 'edd_download_files' => array(), + '_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, + 'edd_sku' => 'sku_0012' + ); + foreach( $meta as $key => $value ) { + update_post_meta( $parent_post_id, $key, $value ); + } + + $filename = EDD_PLUGIN_DIR . 'assets/images/loading.gif'; + $parent_post_id = $parent_post_id; + $filetype = wp_check_filetype( basename( $filename ), null ); + $wp_upload_dir = wp_upload_dir(); + + $attachment = array( + 'guid' => $wp_upload_dir['url'] . '/' . basename( $filename ), + 'post_mime_type' => $filetype['type'], + 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ), + 'post_content' => '', + 'post_status' => 'inherit' + ); + $attach_id = wp_insert_attachment( $attachment, basename( $filename ), $parent_post_id ); + require_once( ABSPATH . 'wp-admin/includes/image.php' ); + $attach_data = wp_generate_attachment_metadata( $attach_id, $filename ); + wp_update_attachment_metadata( $attach_id, $attach_data ); + + // Add attachment to the download product files + update_post_meta( $parent_post_id, 'edd_download_files', array( + array( + 'name' => 'Restricted file', + 'file' => wp_get_attachment_url( $attach_id ), + 'condition' => 0, + ) ) + ); + + $this->go_to( get_permalink( $attach_id ) ); + + add_filter( 'wp_die_handler', function() { return 'EDD\Tests\Tests_Query_Filters::some_useless_function'; } ); + ob_start(); + edd_block_attachments(); + $return = ob_get_clean(); + $this->assertEquals( 'wp_die', $return ); + + // Reset to origin + remove_all_filters( 'wp_die_handler' ); + add_filter( 'wp_die_handler', '_default_wp_die_handler' ); + wp_delete_post( $parent_post_id, true ); + wp_delete_attachment( $attach_id, true ); + $this->go_to( '' ); + + } + + /** + * This method has been brought to live to catch the wp_die() function callback. + * This way it will allow us to test function that normally would die(), but now are returning 'wp_die'. + * When testing a function that calls wp_die(), one would normally get a 'E' error in PHPUnit. + * + * @since 2.2.4 + */ + public static function some_useless_function( $message = '', $title = '', $args = array() ) { + echo 'wp_die'; + } + + +} diff --git a/tests/tests-register-meta.php b/tests/tests-register-meta.php new file mode 100644 index 00000000000..af90f7f3631 --- /dev/null +++ b/tests/tests-register-meta.php @@ -0,0 +1,128 @@ +meta_handler = EDD_Register_Meta::instance(); + + $this->payment_id = Helpers\EDD_Helper_Payment::create_simple_payment(); + $variable_download = Helpers\EDD_Helper_Download::create_variable_download(); + + $this->download_id = $variable_download->ID; + } + + public function tearDown(): void { + parent::tearDown(); + Helpers\EDD_Helper_Payment::delete_payment( $this->payment_id ); + Helpers\EDD_Helper_Download::delete_download( $this->download_id ); + } + + public function test_intval_wrapper() { + $this->setExpectedIncorrectUsage( 'add_post_meta()/update_post_meta()' ); + + update_post_meta( $this->payment_id, '_edd_payment_customer_id', '90.4' ); + + $this->assertEquals( '90', edd_get_payment_meta( $this->payment_id, '_edd_payment_customer_id', true ) ); + + update_post_meta( $this->payment_id, '_edd_payment_customer_id', '-1.43' ); + $this->assertEquals( '0', edd_get_payment_meta( $this->payment_id, '_edd_payment_customer_id', true ) ); + } + + public function test_sanitize_price_positive_value() { + $price = '9'; + + $sanitized = $this->meta_handler->sanitize_price( $price ); + $this->assertEquals( 9, $sanitized ); + } + + public function test_sanitize_negative_value() { + $price = -1; + + $sanitized = $this->meta_handler->sanitize_price( $price ); + $this->assertEquals( 0, $sanitized ); + } + + public function test_sanitize_zero_value() { + // Test saving a zero value + $price = 0; + + $sanitized = $this->meta_handler->sanitize_price( $price ); + $this->assertEquals( 0, $sanitized ); + } + + public function test_sanitize_allow_negative_values_value() { + // Add our filter to allow negative prices. + add_filter( 'edd_allow_negative_prices', '__return_true' ); + $price = -1; + + $sanitized = $this->meta_handler->sanitize_price( $price ); + $this->assertEquals( -1, $sanitized ); + + // Remove our filter. + remove_filter( 'edd_allow_negative_prices', '__return_true' ); + } + + public function test_sanitize_variable_prices() { + $variable_prices = array( + array( 'name' => 'First Option' ), + array( 'amount' => 5, 'name' => 'Second Option' ), + array( 'foo' => 'bar', 'bar' => 'baz' ), + ); + + $sanitized = $this->meta_handler->sanitize_variable_prices( $variable_prices ); + $this->assertEquals( 2, count( $sanitized ) ); + $this->assertEquals( 0, $sanitized[0]['amount'] ); + } + + public function test_sanitize_variable_prices_with_tags() { + $variable_prices = array( + array( 'name' => 'First Option' ), + ); + + $sanitized = $this->meta_handler->sanitize_variable_prices( $variable_prices ); + $this->assertEquals( 'First Option', $sanitized[0]['name'] ); + } + + public function test_sanitize_files() { + $files = array( + array( + 'file' => '', + 'name' => '', + ), + array( + 'file' => ' file2.zip ', + 'name' => 'File 2', + ), + array( + 'file' => 'file3.zip', + 'name' => ' File 3 ', + ), + ); + + $sanitized = $this->meta_handler->sanitize_files( $files ); + $this->assertEquals( 2, count( $sanitized ) ); + $this->assertEquals( 'file2.zip', $sanitized[1]['file'] ); + $this->assertEquals( 'File 3', $sanitized[2]['name'] ); + } + + +} diff --git a/tests/tests-registry.php b/tests/tests-registry.php new file mode 100644 index 00000000000..4df45fd5519 --- /dev/null +++ b/tests/tests-registry.php @@ -0,0 +1,178 @@ +mockRegistry = $this->getMockForAbstractClass( '\EDD\Utils\Registry' ); + } + + /** + * Runs after each test to reset the items array. + * + * @access public + */ + public function tearDown(): void { + $this->mockRegistry->exchangeArray( array() ); + + parent::tearDown(); + } + + /** + * @covers ::add_item() + */ + public function test_add_item_with_empty_attributes_should_return_false() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The attributes were missing when attempting to add the 'foo' item." ); + $this->assertFalse( $this->mockRegistry->add_item( 'foo', array() ) ); + } + + /** + * @covers ::add_item() + * @throws \EDD_Exception + */ + public function test_add_item_with_empty_attributes_should_throw_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The attributes were missing when attempting to add the 'foo' item." ); + + $this->mockRegistry->add_item( 'foo', array() ); + } + + /** + * @covers ::add_item() + * @throws \EDD_Exception + */ + public function test_add_item_with_non_empty_attributes_should_return_true() { + $result = $this->mockRegistry->add_item( 'foo', array( 'bar' ) ); + + $this->assertTrue( $result ); + } + + /** + * @covers ::add_item() + * @throws \EDD_Exception + */ + public function test_add_item_should_register_the_item() { + $this->mockRegistry->add_item( 'foobar', array( + 'class' => 'Foo\Bar', + 'file' => 'path/to/foobar.php' + ) ); + + $this->assertArrayHasKey( 'foobar', $this->mockRegistry->get_items() ); + } + + /** + * @covers ::remove_item() + * @throws \EDD_Exception + */ + public function test_remove_item_with_invalid_item_id_should_effect_no_change() { + $this->mockRegistry->add_item( 'foo', array( 'bar' ) ); + + $this->mockRegistry->remove_item( 'bar' ); + + $this->assertTrue( $this->mockRegistry->offsetExists( 'foo' ) ); + } + + /** + * @covers ::remove_item() + * @throws \EDD_Exception + */ + public function test_remove_item_with_valid_item_id_should_remove_that_item() { + $this->mockRegistry->add_item( 'foo', array( 'bar' ) ); + + $this->mockRegistry->remove_item( 'foo' ); + + $this->assertFalse( $this->mockRegistry->offsetExists( 'foo' ) ); + } + + /** + * @covers ::get_item() + * @throws \EDD_Exception + */ + public function test_get_item_with_invalid_item_id_should_return_an_empty_array() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' item does not exist." ); + + $result = $this->mockRegistry->get_item( 'foo' ); + + $this->assertEqualSets( array(), $result ); + } + + /** + * @covers ::get_item() + * @throws \EDD_Exception + */ + public function test_get_item_with_invalid_item_id_should_throw_an_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' item does not exist." ); + + $this->mockRegistry->get_item( 'foo' ); + } + + /** + * @covers ::get_item() + * @throws \EDD_Exception + */ + public function test_get_item_with_valid_item_id_should_return_that_item() { + $this->mockRegistry->add_item( 'foo', array( 'key' => 'value' ) ); + + $expected = array( + 'key' => 'value' + ); + + $this->assertEqualSetsWithIndex( $expected, $this->mockRegistry->get_item( 'foo' ) ); + } + + /** + * @covers ::get_items() + */ + public function test_get_items_should_be_empty_with_no_registered_items() { + $this->assertEqualSets( array(), $this->mockRegistry->get_items() ); + } + + /** + * @covers ::get_items() + * @throws \EDD_Exception + */ + public function test_get_items_should_return_registered_items() { + $item = array( + 'foobar' => array( + 'class' => 'Foo\Bar', + 'file' => 'path/to/foobar.php' + ) + ); + + // Add a item. + $this->mockRegistry->add_item( 'foobar', array( + 'class' => 'Foo\Bar', + 'file' => 'path/to/foobar.php' + ) ); + + // Confirm the item is retrieved. + $this->assertEqualSets( $item, $this->mockRegistry->get_items() ); + } + +} diff --git a/tests/tests-roles.php b/tests/tests-roles.php new file mode 100755 index 00000000000..197658c9318 --- /dev/null +++ b/tests/tests-roles.php @@ -0,0 +1,114 @@ +assertArrayHasKey( 'shop_manager', (array) $wp_roles->role_names ); + $this->assertArrayHasKey( 'shop_accountant', (array) $wp_roles->role_names ); + $this->assertArrayHasKey( 'shop_worker', (array) $wp_roles->role_names ); + $this->assertArrayHasKey( 'shop_vendor', (array) $wp_roles->role_names ); + } + + public function test_shop_manager_caps() { + global $wp_roles; + + if ( class_exists( 'WP_Roles' ) ) { + if ( ! isset( $wp_roles ) ) { + $wp_roles = new \WP_Roles(); + } + } + + $this->assertArrayHasKey( 'read', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'unfiltered_html', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'upload_files', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'export', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'import', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_others_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_others_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_private_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_private_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_published_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_published_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_others_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_others_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_private_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_private_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_published_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_published_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'manage_categories', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'manage_links', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'moderate_comments', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'publish_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'publish_posts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'read_private_pages', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'view_shop_sensitive_data', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'export_shop_reports', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'manage_shop_settings', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'manage_shop_discounts', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + } + + public function test_administrator_caps() { + global $wp_roles; + + if ( class_exists( 'WP_Roles' ) ) { + if ( ! isset( $wp_roles ) ) { + $wp_roles = new \WP_Roles(); + } + } + + $this->assertArrayHasKey( 'view_shop_sensitive_data', (array) $wp_roles->roles['administrator']['capabilities'] ); + $this->assertArrayHasKey( 'export_shop_reports', (array) $wp_roles->roles['administrator']['capabilities'] ); + $this->assertArrayHasKey( 'manage_shop_settings', (array) $wp_roles->roles['administrator']['capabilities'] ); + $this->assertArrayHasKey( 'manage_shop_discounts', (array) $wp_roles->roles['administrator']['capabilities'] ); + } + + public function test_shop_accountant_caps() { + global $wp_roles; + + if ( class_exists( 'WP_Roles' ) ) { + if ( ! isset( $wp_roles ) ) { + $wp_roles = new \WP_Roles(); + } + } + + $this->assertArrayHasKey( 'read', (array) $wp_roles->roles['shop_accountant']['capabilities'] ); + $this->assertArrayHasKey( 'edit_posts', (array) $wp_roles->roles['shop_accountant']['capabilities'] ); + $this->assertArrayHasKey( 'delete_posts', (array) $wp_roles->roles['shop_accountant']['capabilities'] ); + $this->assertArrayHasKey( 'read_private_products', (array) $wp_roles->roles['shop_accountant']['capabilities'] ); + $this->assertArrayHasKey( 'view_shop_reports', (array) $wp_roles->roles['shop_accountant']['capabilities'] ); + $this->assertArrayHasKey( 'export_shop_reports', (array) $wp_roles->roles['shop_accountant']['capabilities'] ); + $this->assertArrayHasKey( 'edit_shop_payments', (array) $wp_roles->roles['shop_accountant']['capabilities'] ); + } + + public function test_shop_vendor_caps() { + global $wp_roles; + + if ( class_exists( 'WP_Roles' ) ) { + if ( ! isset( $wp_roles ) ) { + $wp_roles = new \WP_Roles(); + } + } + + $this->assertArrayHasKey( 'edit_product', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_product', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'delete_products', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'publish_products', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'edit_published_products', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'upload_files', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + $this->assertArrayHasKey( 'assign_product_terms', (array) $wp_roles->roles['shop_manager']['capabilities'] ); + } +} diff --git a/tests/tests-scripts.php b/tests/tests-scripts.php new file mode 100644 index 00000000000..bc7b17c2af0 --- /dev/null +++ b/tests/tests-scripts.php @@ -0,0 +1,194 @@ +assertNotFalse( has_action( 'init', 'edd_register_scripts' ) ); + $this->assertNotFalse( has_action( 'init', 'edd_register_styles' ) ); + $this->assertNotFalse( has_action( 'wp_enqueue_scripts', 'edd_load_scripts' ) ); + $this->assertNotFalse( has_action( 'wp_enqueue_scripts', 'edd_enqueue_styles' ) ); + $this->assertNotFalse( has_action( 'admin_init', 'edd_register_admin_scripts' ) ); + $this->assertNotFalse( has_action( 'admin_init', 'edd_register_admin_styles' ) ); + $this->assertNotFalse( has_action( 'admin_enqueue_scripts', 'edd_enqueue_admin_scripts' ) ); + $this->assertNotFalse( has_action( 'admin_enqueue_scripts', 'edd_enqueue_admin_styles' ) ); + $this->assertNotFalse( has_action( 'admin_head', 'edd_admin_downloads_icon' ) ); + } + + /** + * Test that all the scripts are loaded at the checkout page. + * + * @since 2.3.6 + */ + public function test_load_scripts_checkout() { + + // Prepare test + $this->go_to( edd_get_checkout_uri() ); + edd_load_scripts(); + + $this->assertTrue( wp_script_is( 'creditCardValidator', 'enqueued' ) ); + $this->assertTrue( wp_script_is( 'edd-checkout-global', 'enqueued' ) ); + $this->assertTrue( wp_script_is( 'edd-ajax', 'enqueued' ) ); + + $this->go_to( '/' ); + } + + /** + * Test that the edd_register_styles() function will bail when the 'disable_styles' + * option is set to true. + * + * @since 2.3.6 + */ + public function test_register_styles_bail_option() { + + // Prepare test + $origin_disable_styles = edd_get_option( 'disable_styles', false ); + edd_update_option( 'disable_styles', true ); + + // Assert + $this->assertNull( edd_register_styles() ); + + // Reset to origin + edd_update_option( 'disable_styles', $origin_disable_styles ); + } + + /** + * Test that the edd_register_styles() function will enqueue the styles. + * + * @since 2.3.6 + */ + public function test_register_styles() { + + edd_update_option( 'disable_styles', false ); + edd_register_styles(); + + $this->assertTrue( wp_style_is( 'edd-styles', 'registered' ) ); + } + + /** + * Test that the test_enqueue_styles() function will enqueue the styles. + * + * @since 2.3.6 + */ + public function test_enqueue_styles() { + + edd_update_option( 'disable_styles', false ); + edd_enqueue_styles(); + + $this->assertTrue( wp_style_is( 'edd-styles', 'enqueued' ) ); + } + + /** + * Test that the edd_register_styles() function will enqueue the proper styles + * when page is checkout + ssl. + * + * @since 2.3.6 + */ + public function test_register_styles_checkout_ssl() { + + // Prepare test + $_SERVER['HTTPS'] = 'ON'; // Fake SSL + $this->go_to( get_permalink( edd_get_option( 'purchase_page' ) ) ); + edd_update_option( 'disable_styles', false ); + edd_register_styles(); + + $this->go_to( '/' ); + + $this->assertTrue( wp_style_is( 'edd-styles', 'registered' ) ); + + unset( $_SERVER['HTTPS'] ); + } + + /** + * Test that the edd_load_admin_scripts() function will bail when not a EDD admin page. + * + * @since 2.3.6 + */ + public function test_load_admin_scripts_bail() { + + // Prepare test + global $pagenow; + $origin_pagenow = $pagenow; + $pagenow = 'dashboard'; + + if ( ! function_exists( 'edd_is_admin_page' ) ) { + include EDD_PLUGIN_DIR . 'includes/admin/admin-pages.php'; + } + + // Assert + $this->assertNull( edd_load_admin_scripts( 'dashboard' ) ); + + // Reset to origin + $pagenow = $origin_pagenow; + } + + /** + * @dataProvider _admin_scripts_dp + * + * @param string $script Registered script handle. + * @param string $script_is Status of the script to check. + */ + public function test_load_admin_scripts_should_enqueue_expected_scripts( $script, $script_is ) { + $this->load_admin_scripts(); + + $this->assertTrue( wp_script_is( $script, $script_is ) ); + } + + /** + * Data provider for test_load_admin_scripts_should_enqueue_expected_scripts(). + */ + public function _admin_scripts_dp() { + return array( + array( 'jquery-chosen', 'enqueued' ), + array( 'edd-admin-scripts', 'enqueued' ), + array( 'jquery-ui-datepicker', 'enqueued' ), + array( 'jquery-ui-dialog', 'enqueued' ), + array( 'media-upload', 'enqueued' ), + array( 'thickbox', 'enqueued' ), + ); + } + + /** + * @dataProvider _admin_styles_dp + * + * @param string $style Registered stylesheet handle. + * @param string $style_is Status of the stylesheet to check. + */ + public function test_load_admin_scripts_should_enqueue_expected_stylesheets( $style, $style_is ) { + $this->load_admin_scripts(); + + $this->assertTrue( wp_style_is( $style, $style_is ) ); + } + + /** + * Data provider for test_load_admin_scripts_should_enqueue_expected_stylesheets(). + */ + public function _admin_styles_dp() { + return array( + array( 'jquery-chosen', 'enqueued' ), + array( 'wp-color-picker', 'enqueued' ), + array( 'thickbox', 'enqueued' ), + array( 'edd-admin', 'enqueued' ), + ); + } + + /** + * Helper to load admin scripts. + */ + protected function load_admin_scripts() { + $this->go_to( edd_get_admin_url( + array( + 'page' => 'edd-settings', + ) + ) ); + edd_load_admin_scripts( 'settings.php' ); + } + +} diff --git a/tests/tests-shortcodes.php b/tests/tests-shortcodes.php new file mode 100755 index 00000000000..e5754e3f0ad --- /dev/null +++ b/tests/tests-shortcodes.php @@ -0,0 +1,325 @@ +user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( self::$user_id ); + + $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 ); + } + + $post = get_post( $post_id ); + + /** 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' => $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' => $post->ID, + 'item_number' => array( + 'id' => $post->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + '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() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'complete' + ); + + $_SERVER['REMOTE_ADDR'] = '10.0.0.0'; + $_SERVER['SERVER_NAME'] = 'edd-virtual.local'; + + remove_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999, 3 ); + + $payment_id = edd_insert_payment( $purchase_data ); + + add_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999, 3 ); + + edd_update_order( $payment_id, array( + 'user_id' => $user->ID + ) ); + + self::$payment_key = $purchase_data['purchase_key']; + + // Remove the account pending filter to only show once in a thread + remove_filter( 'edd_allow_template_part_account_pending', 'edd_load_verification_template_once', 10, 1 ); + } + + public function setup(): void { + parent::setUp(); + + wp_set_current_user( self::$user_id ); + + // Remove the account pending filter to only show once in a thread + remove_filter( 'edd_allow_template_part_account_pending', 'edd_load_verification_template_once', 10, 1 ); + } + + public static function tearDownAfterClass(): void { + parent::tearDownAfterClass(); + unset( $_SERVER['REMOTE_ADDR'] ); + unset( $_SERVER['SERVER_NAME'] ); + } + + public function test_shortcodes_are_registered() { + global $shortcode_tags; + + $this->assertArrayHasKey( 'purchase_link', $shortcode_tags ); + $this->assertArrayHasKey( 'download_history', $shortcode_tags ); + $this->assertArrayHasKey( 'purchase_history', $shortcode_tags ); + $this->assertArrayHasKey( 'download_checkout', $shortcode_tags ); + $this->assertArrayHasKey( 'download_cart', $shortcode_tags ); + $this->assertArrayHasKey( 'edd_login', $shortcode_tags ); + $this->assertArrayHasKey( 'download_discounts', $shortcode_tags ); + $this->assertArrayHasKey( 'purchase_collection', $shortcode_tags ); + $this->assertArrayHasKey( 'downloads', $shortcode_tags ); + $this->assertArrayHasKey( 'edd_price', $shortcode_tags ); + $this->assertArrayHasKey( 'edd_receipt', $shortcode_tags ); + $this->assertArrayHasKey( 'edd_profile_editor', $shortcode_tags ); + } + + public function test_download_history() { + $actual = edd_download_history(); + + $this->assertIsString( $actual ); + $this->assertStringContainsString( '

    ', $actual ); + + edd_set_user_to_pending( self::$user_id ); + + $this->assertStringContainsString( '

    ', $actual ); + + edd_set_user_to_pending( self::$user_id ); + + $this->assertStringContainsString( '

    ', $actual ); + } + + public function test_cart_shortcode() { + $actual = edd_cart_shortcode( array() ); + + $this->assertIsString( $actual ); + $this->assertStringContainsString( '
      ', $actual ); + } + + public function test_login_form() { + $purchase_history_page = edd_get_option( 'purchase_history_page' ); + + $actual = edd_login_form_shortcode( array() ); + + $this->assertIsString( $actual ); + $this->assertStringContainsString( '

      You are already logged in

      ', $actual ); + + // Log out the user so we can see the login form + wp_set_current_user( 0 ); + + $args = array( + 'redirect' => get_option( 'site_url' ), + ); + + $login_form = edd_login_form_shortcode( $args ); + $this->assertIsString( $login_form ); + $this->assertStringContainsString( '"' . get_option( 'site_url' ) . '"', $login_form ); + + edd_update_option( 'login_redirect_page', $purchase_history_page ); + + $login_form = edd_login_form_shortcode( array() ); + $this->assertIsString( $login_form ); + $this->assertStringContainsString( '"' . get_permalink( $purchase_history_page ) . '"', $login_form ); + } + + public function test_purchase_collection_shortcode() { + $this->go_to( '/' ); + + $actual = edd_purchase_collection_shortcode( array() ); + + $this->assertIsString( $actual ); + $this->assertEquals( '
      Purchase All Items', $actual ); + } + + public function test_download_price_shortcode() { + $post_id = self::factory()->post->create( array( 'post_type' => 'download' ) ); + + $meta = array( + 'edd_price' => '54.43', + ); + + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + $actual = edd_download_price_shortcode( array( 'id' => $post_id ) ); + + $this->assertIsString( $actual ); + $this->assertEquals( '$54.43', $actual ); + } + + public function __test_receipt_shortcode() { + /** + * @internal This test fails on Travis but passes when running locally. + */ + +// $actual = edd_receipt_shortcode( array( 'payment_key' => self::$payment_key ) ); +// +// $this->assertIsString( $actual ); +// $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( '