diff --git a/vendor/johnbillion/args/LICENSE b/vendor/johnbillion/args/LICENSE new file mode 100644 index 0000000..4522ba0 --- /dev/null +++ b/vendor/johnbillion/args/LICENSE @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {{description}} + Copyright (C) {{year}} {{fullname}} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/johnbillion/args/README.md b/vendor/johnbillion/args/README.md new file mode 100644 index 0000000..b0f5d8e --- /dev/null +++ b/vendor/johnbillion/args/README.md @@ -0,0 +1,248 @@ +[![](https://img.shields.io/github/actions/workflow/status/johnbillion/args/tests.yml?branch=trunk&style=flat-square)](https://github.com/johnbillion/args/actions) + +# Args + +Many functions and methods in WordPress accept arguments as an associative array which your IDE or code editor cannot autocomplete like it does for individual function parameters. + +```php +$query = new WP_Query( [ + 'post_type' => 'post', + 'category' => 'does this accept an ID or a slug?', + 'number_of_...errr' +] ); +``` + +This library provides well-documented classes which represent many of the associative array parameters used throughout WordPress. Using them at the point where you populate the arguments means you get autocompletion and intellisense in your code editor, and strict typing thanks to typed properties in PHP 7.4. Comprehensive types and constraints for [PHPStan](https://phpstan.org/) are also included. + +![](.github/assets/screenshot.png) + +## Current Status + +Last updated for WordPress 6.5. + +## Requirements + +* PHP 7.4 or PHP 8+ + +## Installation + +```shell +composer require johnbillion/args +``` + +## Usage + +Usage with a class constructor: + +```php +$args = new \Args\WP_Query; + +$args->tag = 'amazing'; +$args->posts_per_page = 100; + +$query = new \WP_Query( $args->toArray() ); +``` + +Usage with a procedural function parameter: + +```php +$args = new \Args\register_post_type; + +$args->show_in_rest = true; +$args->taxonomies = [ 'genre', 'audience' ]; + +$story = register_post_type( 'story', $args->toArray() ); +``` + +## Meta Queries, Tax Queries, and Date Queries + +The query classes in WordPress support variously `meta_query`, `tax_query`, and `date_query` arguments. These are fully supported and you can construct them in a structured and strongly typed way. + +Creating a `meta_query` argument: + +```php +$args = new \Args\WP_Query; + +// Create a clause +$clause = new \Args\MetaQuery\Clause; +$clause->key = 'my_meta_key'; +$clause->value = 'my_meta_value'; + +// Add the clause +$args->meta_query->clauses[] = $clause; + +$query = new \WP_Query( $args->toArray() ); +``` + +Creating a `tax_query` argument: + +```php +$args = new \Args\WP_Query; + +// Create a clause +$clause = new \Args\TaxQuery\Clause; +$clause->taxonomy = 'post_tag'; +$clause->terms = [ 'amazing' ]; + +// Add the clause +$args->tax_query->clauses[] = $clause; + +$query = new \WP_Query( $args->toArray() ); +``` + +Creating a `date_query` argument: + +```php +$args = new \Args\WP_Query; + +// Create a clause +$clause = new \Args\DateQuery\Clause; +$clause->year = 2000; +$clause->compare = '>='; + +// Add the clause +$args->date_query->clauses[] = $clause; + +$query = new \WP_Query( $args->toArray() ); +``` + +Alternatively you can construct a complete query object by calling the `fromArray()` static method with the same nested array syntax that WordPress core uses: + +```php +$args = new \Args\WP_Query; + +// Set the meta query from an array +$array = [ + [ + 'key' => 'my_meta_key', + 'value' => 'my_meta_value', + ] +]; +$args->meta_query = $args->meta_query::fromArray( $array ); + +$query = new \WP_Query( $args->toArray() ); +``` + +## What's Provided + +### Posts + +* `\Args\WP_Query` +* `\Args\register_post_type` +* `\Args\wp_insert_post` +* `\Args\wp_update_post` +* `\Args\get_posts` +* `\Args\register_post_meta` +* `\Args\register_post_status` + +### Taxonomies and Terms + +* `\Args\WP_Term_Query` +* `\Args\register_taxonomy` +* `\Args\wp_insert_term` +* `\Args\wp_update_term` +* `\Args\get_terms` +* `\Args\get_categories` +* `\Args\get_tags` +* `\Args\register_term_meta` +* `\Args\wp_count_terms` +* `\Args\wp_get_object_terms` +* `\Args\wp_dropdown_categories` + +### Users + +* `\Args\WP_User_Query` +* `\Args\wp_insert_user` +* `\Args\wp_update_user` +* `\Args\get_users` + +### Comments + +* `\Args\WP_Comment_Query` +* `\Args\get_comments` + +### HTTP API + +* `\Args\wp_remote_get` +* `\Args\wp_remote_post` +* `\Args\wp_remote_head` +* `\Args\wp_remote_request` +* `\Args\wp_safe_remote_get` +* `\Args\wp_safe_remote_post` +* `\Args\wp_safe_remote_head` +* `\Args\wp_safe_remote_request` + +### Blocks + +* `\Args\WP_Block_Type` +* `\Args\register_block_type` + +### Customizer + +* `\Args\WP_Customize_Control` +* `\Args\WP_Customize_Manager` +* `\Args\WP_Customize_Panel` +* `\Args\WP_Customize_Section` +* `\Args\WP_Customize_Setting` + +### Everything Else + +* `\Args\paginate_links` +* `\Args\register_meta` +* `\Args\register_rest_field` +* `\Args\wp_get_nav_menus` +* `\Args\wp_nav_menu` +* `\Args\wp_die` +* `\Args\wp_dropdown_languages` +* `\Args\wp_generate_tag_cloud` + +## Type Checking + +Typed class properties are implemented in this library where possible. If you pass a value of the wrong type to an argument that is typed, you'll get a fatal error as long as you're using strict types: + +```php +=7.4" + }, + "autoload": { + "psr-4": { + "Args\\": "src/" + } + }, + "scripts": { + "generate": [ + "@php bin/generate.php" + ], + "test:shapes": [ + "@php bin/shapes.php" + ], + "test:shape": [ + "@php bin/shape.php" + ], + "test:phpcs": [ + "phpcs -p --basepath='./' ." + ], + "test:phpunit": [ + "phpunit --testdox" + ], + "test:phpstan": [ + "phpstan analyze" + ], + "test": [ + "@test:shapes", + "@test:phpcs", + "@test:phpunit", + "@test:phpstan" + ] + }, + "extra": { + "args-shapes": [ + "--function=\"\\get_categories()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/category.php", + "--function=\"\\get_comments()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/comment.php", + "--function=\"\\get_posts()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/post.php", + "--function=\"\\get_tags()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/category.php", + "--function=\"\\get_terms()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/taxonomy.php", + "--function=\"\\get_users()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/user.php", + "--function=\"\\paginate_links()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/general-template.php", + "--function=\"\\register_block_type()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/blocks.php", + "--function=\"\\register_meta()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/meta.php", + "--function=\"\\register_post_meta()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/post.php", + "--function=\"\\register_post_status()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/post.php", + "--function=\"\\register_post_type()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/post.php", + "--function=\"\\register_rest_field()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/rest-api.php", + "--function=\"\\register_taxonomy()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/taxonomy.php", + "--function=\"\\register_term_meta()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/taxonomy.php", + "--function=\"\\wp_count_terms()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/taxonomy.php", + "--function=\"\\wp_die()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/functions.php", + "--function=\"\\wp_dropdown_categories()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/category-template.php", + "--function=\"\\wp_dropdown_languages()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/l10n.php", + "--function=\"\\wp_generate_tag_cloud()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/category-template.php", + "--function=\"\\wp_get_nav_menus()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/nav-menu.php", + "--function=\"\\wp_get_object_terms()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/taxonomy.php", + "--function=\"\\wp_insert_post()\" --param=postarr --file=vendor/wordpress/wordpress/wp-includes/post.php", + "--function=\"\\wp_insert_term()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/taxonomy.php", + "--function=\"\\wp_insert_user()\" --param=userdata --file=vendor/wordpress/wordpress/wp-includes/user.php", + "--function=\"\\wp_nav_menu()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/nav-menu-template.php", + "--function=\"\\wp_remote_get()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/http.php", + "--function=\"\\wp_remote_head()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/http.php", + "--function=\"\\wp_remote_post()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/http.php", + "--function=\"\\wp_remote_request()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/http.php", + "--function=\"\\wp_safe_remote_get()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/http.php", + "--function=\"\\wp_safe_remote_head()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/http.php", + "--function=\"\\wp_safe_remote_post()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/http.php", + "--function=\"\\wp_safe_remote_request()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/http.php", + "--function=\"\\wp_update_post()\" --param=postarr --file=vendor/wordpress/wordpress/wp-includes/post.php", + "--function=\"\\wp_update_term()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/taxonomy.php", + "--function=\"\\wp_update_user()\" --param=userdata --file=vendor/wordpress/wordpress/wp-includes/user.php", + "--method=\"\\WP_Block_Type::__construct()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/class-wp-block-type.php", + "--method=\"\\WP_Comment_Query::__construct()\" --param=query --file=vendor/wordpress/wordpress/wp-includes/class-wp-comment-query.php", + "--method=\"\\WP_Customize_Control::__construct()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/class-wp-customize-control.php", + "--method=\"\\WP_Customize_Manager::__construct()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/class-wp-customize-manager.php", + "--method=\"\\WP_Customize_Panel::__construct()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/class-wp-customize-panel.php", + "--method=\"\\WP_Customize_Section::__construct()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/class-wp-customize-section.php", + "--method=\"\\WP_Customize_Setting::__construct()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/class-wp-customize-setting.php", + "--method=\"\\WP_Http::request()\" --param=args --file=vendor/wordpress/wordpress/wp-includes/class-wp-http.php", + "--method=\"\\WP_Query::parse_query()\" --param=query --file=vendor/wordpress/wordpress/wp-includes/class-wp-query.php", + "--method=\"\\WP_Term_Query::__construct()\" --param=query --file=vendor/wordpress/wordpress/wp-includes/class-wp-term-query.php", + "--method=\"\\WP_User_Query::prepare_query()\" --param=query --file=vendor/wordpress/wordpress/wp-includes/class-wp-user-query.php" + ], + "wordpress-install-dir": "vendor/wordpress/wordpress" + }, + "require-dev": { + "ergebnis/json-printer": "^3.2", + "ergebnis/phpstan-rules": "^1.0", + "humanmade/coding-standards": "^1.1", + "johnbillion/falsey-assertequals-detector": "^3", + "phpdocumentor/reflection": "~4.0 || ~5.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^9.0", + "roots/wordpress-core-installer": "^1.0.0", + "roots/wordpress-full": "~6.5.0" + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/johnbillion" + } + ] +} diff --git a/vendor/johnbillion/args/src/Arrayable/Arrayable.php b/vendor/johnbillion/args/src/Arrayable/Arrayable.php new file mode 100644 index 0000000..26d0de4 --- /dev/null +++ b/vendor/johnbillion/args/src/Arrayable/Arrayable.php @@ -0,0 +1,15 @@ + + */ + public function toArray() :? array; +} diff --git a/vendor/johnbillion/args/src/Arrayable/ProvidesFromArray.php b/vendor/johnbillion/args/src/Arrayable/ProvidesFromArray.php new file mode 100644 index 0000000..49f5761 --- /dev/null +++ b/vendor/johnbillion/args/src/Arrayable/ProvidesFromArray.php @@ -0,0 +1,25 @@ + $args + * @return static + */ + final public static function fromArray( array $args ) : self { + $class = new static(); + + foreach ( $args as $key => $value ) { + $class->$key = $value; + } + + return $class; + } + +} diff --git a/vendor/johnbillion/args/src/Arrayable/ProvidesToArray.php b/vendor/johnbillion/args/src/Arrayable/ProvidesToArray.php new file mode 100644 index 0000000..0f330c6 --- /dev/null +++ b/vendor/johnbillion/args/src/Arrayable/ProvidesToArray.php @@ -0,0 +1,52 @@ + */ + protected array $map = []; + + /** + * @return array + */ + final public function getMap() : array { + return $this->map; + } + + /** + * @return array + */ + final public function toArray() : array { + $vars = get_object_vars( $this ); + + foreach ( $this->getMap() as $from => $to ) { + if ( array_key_exists( $from, $vars ) ) { + $vars[ $to ] = $vars[ $from ]; + unset( $vars[ $from ] ); + } + } + + unset( $vars['map'] ); + + foreach ( $vars as $key => $var ) { + if ( ! $var instanceof Arrayable ) { + continue; + } + + $vars[ $key ] = $var->toArray(); + } + + $vars = array_filter( $vars, fn( $value ) : bool => $value !== null ); + + ksort( $vars ); + + return $vars; + } + +} diff --git a/vendor/johnbillion/args/src/DateQuery/Clause.php b/vendor/johnbillion/args/src/DateQuery/Clause.php new file mode 100644 index 0000000..10623c4 --- /dev/null +++ b/vendor/johnbillion/args/src/DateQuery/Clause.php @@ -0,0 +1,179 @@ + + * @phpstan-var string|array{ + * year: numeric-string, + * month?: numeric-string, + * day?: numeric-string, + * } + */ + public $before; + + /** + * Date to retrieve posts after. Accepts `strtotime()`-compatible string, or array of 'year', 'month', 'day' values. + * + * @var string|array + * @phpstan-var string|array{ + * year: numeric-string, + * month?: numeric-string, + * day?: numeric-string, + * } + */ + public $after; + + /** + * Used to add a clause comparing a column other than + * the column specified in the top-level `$column` parameter. + * See WP_Date_Query::validate_column() and + * the {@see 'date_query_valid_columns'} filter for the list + * of accepted values. + * + * Default is the value of top-level `$column`. + */ + public string $column; + + /** + * 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 '='. + * + * @phpstan-var Values::DATE_QUERY_COMPARE_* + */ + public string $compare; + + /** + * Include results from dates specified in 'before' or 'after'. + * + * Default false. + */ + public bool $inclusive; + + /** + * The four-digit year number. Accepts any four-digit year + * or an array of years if `$compare` supports it. + * + * Default empty. + * + * @var int|int[] + */ + public $year; + + /** + * The two-digit month number. Accepts numbers 1-12 or an + * array of valid numbers if `$compare` supports it. + * + * Default empty. + * + * @var int|int[] + * @phpstan-var int<1,12>|array> + */ + public $month; + + /** + * The week number of the year. Accepts numbers 0-53 or an + * array of valid numbers if `$compare` supports it. + * + * Default empty. + * + * @var int|int[] + * @phpstan-var int<0,53>|array> + */ + public $week; + + /** + * The day number of the year. Accepts numbers 1-366 or an + * array of valid numbers if `$compare` supports it. + * + * Default empty. + * + * @var int|int[] + * @phpstan-var int<1,366>|array> + */ + public $dayofyear; + + /** + * The day of the month. Accepts numbers 1-31 or an array + * of valid numbers if `$compare` supports it. + * + * Default empty. + * + * @var int|int[] + * @phpstan-var int<1,31>|array> + */ + public $day; + + /** + * 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. + * + * @var int|int[] + * @phpstan-var int<1,7>|array> + */ + public $dayofweek; + + /** + * 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. + * + * @var int|int[] + * @phpstan-var int<1,7>|array> + */ + public $dayofweek_iso; + + /** + * The hour of the day. Accepts numbers 0-23 or an array + * of valid numbers if `$compare` supports it. + * + * Default empty. + * + * @var int|int[] + * @phpstan-var int<1,23>|array> + */ + public $hour; + + /** + * The minute of the hour. Accepts numbers 0-59 or an array + * of valid numbers if `$compare` supports it. + * + * Default empty. + * + * @var int|int[] + * @phpstan-var int<0,59>|array> + */ + public $minute; + + /** + * The second of the minute. Accepts numbers 0-59 or an + * array of valid numbers if `$compare` supports it. + * + * Default empty. + * + * @var int|int[] + * @phpstan-var int<0,59>|array> + */ + public $second; + +} diff --git a/vendor/johnbillion/args/src/DateQuery/ProvidesArgs.php b/vendor/johnbillion/args/src/DateQuery/ProvidesArgs.php new file mode 100644 index 0000000..bfc0330 --- /dev/null +++ b/vendor/johnbillion/args/src/DateQuery/ProvidesArgs.php @@ -0,0 +1,19 @@ +date_query = $date_query; + } +} diff --git a/vendor/johnbillion/args/src/DateQuery/Query.php b/vendor/johnbillion/args/src/DateQuery/Query.php new file mode 100644 index 0000000..b8972c9 --- /dev/null +++ b/vendor/johnbillion/args/src/DateQuery/Query.php @@ -0,0 +1,100 @@ +', '>=', '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. + * + * Default '='. + * + * @phpstan-var Values::DATE_QUERY_COMPARE_* + */ + public string $compare; + + /** + * The MySQL keyword used to join the clauses of the query. Accepts 'AND' or 'OR'. + * + * Default 'AND'. + * + * @phpstan-var Values::DATE_QUERY_RELATION_* + */ + public string $relation; + + /** + * @var array + */ + public array $clauses; + + /** + * @param mixed[] $clauses + * @return static + */ + final public static function fromArray( array $clauses ) : self { + $class = new static(); + + foreach ( $clauses as $key => $value ) { + if ( 'column' === $key ) { + $class->column = $value; + } elseif ( 'compare' === $key ) { + $class->compare = $value; + } elseif ( 'relation' === $key ) { + $class->relation = $value; + } else { + $class->addClause( Clause::fromArray( $value ) ); + } + } + + return $class; + } + + final public function addClause( Clause $clause ) : void { + $this->clauses[] = $clause; + } + + /** + * @return ?array + */ + final public function toArray() :? array { + if ( ! isset( $this->clauses ) || count( $this->clauses ) === 0 ) { + return null; + } + + $vars = []; + + if ( isset( $this->column ) ) { + $vars['column'] = $this->column; + } + if ( isset( $this->compare ) ) { + $vars['compare'] = $this->compare; + } + if ( isset( $this->relation ) ) { + $vars['relation'] = $this->relation; + } + + foreach ( $this->clauses as $key => $value ) { + $vars[ $key ] = $value->toArray(); + } + + return $vars; + } + +} diff --git a/vendor/johnbillion/args/src/DateQuery/Values.php b/vendor/johnbillion/args/src/DateQuery/Values.php new file mode 100644 index 0000000..100ab1c --- /dev/null +++ b/vendor/johnbillion/args/src/DateQuery/Values.php @@ -0,0 +1,24 @@ +'; + public const DATE_QUERY_COMPARE_GREATER_THAN_OR_EQUALS = '>='; + public const DATE_QUERY_COMPARE_LESS_THAN = '<'; + public const DATE_QUERY_COMPARE_LESS_THAN_OR_EQUALS = '<='; + public const DATE_QUERY_COMPARE_IN = 'IN'; + public const DATE_QUERY_COMPARE_NOT_IN = 'NOT IN'; + public const DATE_QUERY_COMPARE_BETWEEN = 'BETWEEN'; + public const DATE_QUERY_COMPARE_NOT_BETWEEN = 'NOT BETWEEN'; +} diff --git a/vendor/johnbillion/args/src/DateQuery/WithArgs.php b/vendor/johnbillion/args/src/DateQuery/WithArgs.php new file mode 100644 index 0000000..3006df2 --- /dev/null +++ b/vendor/johnbillion/args/src/DateQuery/WithArgs.php @@ -0,0 +1,12 @@ + + */ + public $key; + + /** + * Meta value or values to filter by. + * + * @var string|array + */ + public $value; + + /** + * MySQL operator used for comparing the meta value. + * + * Default is 'IN' when `value` is an array, '=' otherwise. + * + * @phpstan-var Values::META_COMPARE_VALUE_* + */ + public string $compare; + + /** + * MySQL operator used for comparing the meta key. + * + * Default is 'IN' when `key` is an array, '=' otherwise. + * + * @phpstan-var Values::META_COMPARE_KEY_* + */ + public string $compare_key; + + /** + * MySQL data type that the `meta_value` column will be CAST to for comparisons. + * + * @phpstan-var Values::META_TYPE_VALUE_* + */ + public string $type; + + /** + * MySQL data type that the `meta_key` column will be CAST to for comparisons. + * + * @phpstan-var Values::META_TYPE_KEY_* + */ + public string $type_key; + +} diff --git a/vendor/johnbillion/args/src/MetaQuery/ProvidesArgs.php b/vendor/johnbillion/args/src/MetaQuery/ProvidesArgs.php new file mode 100644 index 0000000..bfef5bc --- /dev/null +++ b/vendor/johnbillion/args/src/MetaQuery/ProvidesArgs.php @@ -0,0 +1,65 @@ + + */ + public $meta_key; + + /** + * Meta value or values to filter by. + * + * @var string|array + */ + public $meta_value; + + /** + * MySQL operator used for comparing the meta value. + * + * Default is 'IN' when `meta_value` is an array, '=' otherwise. + * + * @phpstan-var Values::META_COMPARE_VALUE_* + */ + public string $meta_compare; + + /** + * MySQL operator used for comparing the meta key. + * + * Default is 'IN' when `meta_key` is an array, '=' otherwise. + * + * @phpstan-var Values::META_COMPARE_KEY_* + */ + public string $meta_compare_key; + + /** + * MySQL data type that the `meta_value` column will be CAST to for comparisons. + * + * @phpstan-var Values::META_TYPE_VALUE_* + */ + public string $meta_type; + + /** + * MySQL data type that the `meta_key` column will be CAST to for comparisons. + * + * @phpstan-var Values::META_TYPE_KEY_* + */ + public string $meta_type_key; + + /** + * A `Query` object representing the `WP_Meta_Query` constructor argument. + */ + public Query $meta_query; + + public function setMetaQuery( Query $meta_query ) : void { + $this->meta_query = $meta_query; + } +} diff --git a/vendor/johnbillion/args/src/MetaQuery/Query.php b/vendor/johnbillion/args/src/MetaQuery/Query.php new file mode 100644 index 0000000..59e88ce --- /dev/null +++ b/vendor/johnbillion/args/src/MetaQuery/Query.php @@ -0,0 +1,76 @@ + + */ + public array $clauses; + + /** + * @param mixed[] $clauses + * @return static + */ + final public static function fromArray( array $clauses ) : self { + $class = new static(); + + foreach ( $clauses as $key => $value ) { + if ( 'relation' === $key ) { + $class->relation = $value; + } elseif ( is_string( $key ) ) { + $class->addClause( Clause::fromArray( $value ), $key ); + } else { + $class->addClause( Clause::fromArray( $value ) ); + } + } + + return $class; + } + + final public function addClause( Clause $clause, string $key = null ) : void { + if ( null !== $key ) { + $this->clauses[ $key ] = $clause; + } else { + $this->clauses[] = $clause; + } + } + + /** + * @return ?array + */ + final public function toArray() :? array { + if ( ! isset( $this->clauses ) || count( $this->clauses ) === 0 ) { + return null; + } + + $vars = []; + + if ( isset( $this->relation ) ) { + $vars['relation'] = $this->relation; + } + + foreach ( $this->clauses as $key => $value ) { + $vars[ $key ] = $value->toArray(); + } + + return $vars; + } + +} diff --git a/vendor/johnbillion/args/src/MetaQuery/Values.php b/vendor/johnbillion/args/src/MetaQuery/Values.php new file mode 100644 index 0000000..de8e838 --- /dev/null +++ b/vendor/johnbillion/args/src/MetaQuery/Values.php @@ -0,0 +1,56 @@ +'; + public const META_COMPARE_VALUE_GREATER_THAN_OR_EQUALS = '>='; + public const META_COMPARE_VALUE_LESS_THAN = '<'; + public const META_COMPARE_VALUE_LESS_THAN_OR_EQUALS = '<='; + public const META_COMPARE_VALUE_LIKE = 'LIKE'; + public const META_COMPARE_VALUE_NOT_LIKE = 'NOT LIKE'; + public const META_COMPARE_VALUE_IN = 'IN'; + public const META_COMPARE_VALUE_NOT_IN = 'NOT IN'; + public const META_COMPARE_VALUE_BETWEEN = 'BETWEEN'; + public const META_COMPARE_VALUE_NOT_BETWEEN = 'NOT BETWEEN'; + public const META_COMPARE_VALUE_REGEXP = 'REGEXP'; + public const META_COMPARE_VALUE_NOT_REGEXP = 'NOT REGEXP'; + public const META_COMPARE_VALUE_RLIKE = 'RLIKE'; + public const META_COMPARE_VALUE_EXISTS = 'EXISTS'; + public const META_COMPARE_VALUE_NOT_EXISTS = 'NOT EXISTS'; + + public const META_TYPE_KEY_NONE = ''; + public const META_TYPE_KEY_BINARY = 'BINARY'; + + public const META_TYPE_VALUE_NUMERIC = 'NUMERIC'; + public const META_TYPE_VALUE_BINARY = 'BINARY'; + public const META_TYPE_VALUE_CHAR = 'CHAR'; + public const META_TYPE_VALUE_DATE = 'DATE'; + public const META_TYPE_VALUE_DATETIME = 'DATETIME'; + public const META_TYPE_VALUE_DECIMAL = 'DECIMAL'; + public const META_TYPE_VALUE_SIGNED = 'SIGNED'; + public const META_TYPE_VALUE_TIME = 'TIME'; + public const META_TYPE_VALUE_UNSIGNED = 'UNSIGNED'; + + public const META_QUERY_RELATION_AND = 'AND'; + public const META_QUERY_RELATION_OR = 'OR'; +} diff --git a/vendor/johnbillion/args/src/MetaQuery/WithArgs.php b/vendor/johnbillion/args/src/MetaQuery/WithArgs.php new file mode 100644 index 0000000..e022efe --- /dev/null +++ b/vendor/johnbillion/args/src/MetaQuery/WithArgs.php @@ -0,0 +1,12 @@ + + * @implements \IteratorAggregate + */ +abstract class Base implements \ArrayAccess, \Countable, \IteratorAggregate, Arrayable { + public const ORDER_ASC = 'ASC'; + public const ORDER_DESC = 'DESC'; + + use \Args\Arrayable\ProvidesFromArray; + use \Args\Arrayable\ProvidesToArray; + + final public function __construct() { + if ( $this instanceof \Args\DateQuery\WithArgs ) { + $this->setDateQuery( new \Args\DateQuery\Query ); + } + if ( $this instanceof \Args\MetaQuery\WithArgs ) { + $this->setMetaQuery( new \Args\MetaQuery\Query ); + } + if ( $this instanceof \Args\TaxQuery\WithArgs ) { + $this->setTaxQuery( new \Args\TaxQuery\Query ); + } + } + + /** + * @param mixed $offset + */ + #[\ReturnTypeWillChange] + final public function offsetExists( $offset ) : bool { + if ( ! is_string( $offset ) ) { + return false; + } + + return array_key_exists( $offset, get_object_vars( $this ) ); + } + + /** + * @param mixed $offset + * @return mixed + */ + #[\ReturnTypeWillChange] + final public function offsetGet( $offset ) { + if ( ! is_string( $offset ) ) { + return null; + } + + if ( ! array_key_exists( $offset, get_object_vars( $this ) ) ) { + return null; + } + + return $this->$offset; + } + + /** + * @param mixed $offset + * @param mixed $value + */ + #[\ReturnTypeWillChange] + final public function offsetSet( $offset, $value ) : void { + $this->$offset = $value; + } + + /** + * @param mixed $offset + */ + #[\ReturnTypeWillChange] + final public function offsetUnset( $offset ) : void { + unset( $this->$offset ); + } + + final public function count() : int { + return count( $this->toArray() ); + } + + final public function getIterator() : \Traversable { + return new \ArrayIterator( $this->toArray() ); + } +} diff --git a/vendor/johnbillion/args/src/TaxQuery/Clause.php b/vendor/johnbillion/args/src/TaxQuery/Clause.php new file mode 100644 index 0000000..6c19121 --- /dev/null +++ b/vendor/johnbillion/args/src/TaxQuery/Clause.php @@ -0,0 +1,65 @@ +|array + */ + public $terms; + + /** + * Field to match `$terms` against. Accepts: + * + * - 'term_id' + * - 'slug' + * - 'name' + * - 'term_taxonomy_id' + * + * Default: 'term_id'. + * + * @phpstan-var 'term_id'|'slug'|'name'|'term_taxonomy_id' + */ + public string $field; + + /** + * MySQL operator to be used with $terms in the WHERE clause. + * + * Accepts: + * + * - 'AND' + * - 'IN' + * - 'NOT IN' + * - 'EXISTS' + * - 'NOT EXISTS' + * + * Default: 'IN'. + * + * @phpstan-var Values::TAX_QUERY_OPERATOR_* + */ + public string $operator; + + /** + * Whether to include child terms. Requires a `$taxonomy`. + * + * Default: true. + */ + public bool $children; +} diff --git a/vendor/johnbillion/args/src/TaxQuery/ProvidesArgs.php b/vendor/johnbillion/args/src/TaxQuery/ProvidesArgs.php new file mode 100644 index 0000000..d04f7b3 --- /dev/null +++ b/vendor/johnbillion/args/src/TaxQuery/ProvidesArgs.php @@ -0,0 +1,19 @@ +tax_query = $tax_query; + } +} diff --git a/vendor/johnbillion/args/src/TaxQuery/Query.php b/vendor/johnbillion/args/src/TaxQuery/Query.php new file mode 100644 index 0000000..e5be8a5 --- /dev/null +++ b/vendor/johnbillion/args/src/TaxQuery/Query.php @@ -0,0 +1,70 @@ + + */ + public array $clauses; + + /** + * @param mixed[] $clauses + * @return static + */ + final public static function fromArray( array $clauses ) : self { + $class = new static(); + + foreach ( $clauses as $key => $value ) { + if ( 'relation' === $key ) { + $class->relation = $value; + } else { + $class->addClause( Clause::fromArray( $value ) ); + } + } + + return $class; + } + + final public function addClause( Clause $clause ) : void { + $this->clauses[] = $clause; + } + + /** + * @return ?array + */ + final public function toArray() :? array { + if ( ! isset( $this->clauses ) || count( $this->clauses ) === 0 ) { + return null; + } + + $vars = []; + + if ( isset( $this->relation ) ) { + $vars['relation'] = $this->relation; + } + + foreach ( $this->clauses as $key => $value ) { + $vars[ $key ] = $value->toArray(); + } + + return $vars; + } + +} diff --git a/vendor/johnbillion/args/src/TaxQuery/Values.php b/vendor/johnbillion/args/src/TaxQuery/Values.php new file mode 100644 index 0000000..3aad1b8 --- /dev/null +++ b/vendor/johnbillion/args/src/TaxQuery/Values.php @@ -0,0 +1,19 @@ + + */ + public array $parent; + + /** + * Setting ancestor makes a block available only inside the specified block types at any position of the ancestor's block subtree. + * + * @var array + */ + public array $ancestor; + + /** + * Block type icon. + */ + public string $icon; + + /** + * A detailed block type description. + */ + public string $description; + + /** + * Additional keywords to produce block type as result in search interfaces. + * + * @var array + */ + public array $keywords; + + /** + * The translation textdomain. + */ + public string $textdomain; + + /** + * Alternative block styles. + * + * @link https://developer.wordpress.org/block-editor/reference-guides/block-api/block-styles/ + * + * @var array> + * @phpstan-var array + */ + public array $styles; + + /** + * Supported features. + * + * @link https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/ + * + * @var array + */ + public array $supports; + + /** + * Structured data for the block preview. + * + * @var array> + */ + public array $example; + + /** + * Block type render callback. + * + * @var callable + * @phpstan-var callable( array, string, \WP_Block= ): string + */ + public $render_callback; + + /** + * Block type attributes property schemas. + * + * @var array> + */ + public array $attributes; + + /** + * Context values inherited by blocks of this type. + * + * @var array + */ + public array $uses_context; + + /** + * Context provided by blocks of this type. + * + * @var array + */ + public array $provides_context; + + /** + * Block hooks. + * + * @var array + * @phpstan-var array + */ + public array $block_hooks; + + /** + * Limits which block types can be inserted as children of this block type + * + * @var array + * @phpstan-var list + */ + public array $allowed_blocks; + + /** + * Block type variations callback. + * + * @var callable + * @phpstan-var callable(): list> + */ + public $variation_callback; + + /** + * Block type front end only style handles. + * + * @var array + * @phpstan-var list + */ + public array $view_style_handles; + + /** + * Block type editor script handle. + * + * @deprecated WordPress 6.1.0 + */ + public string $editor_script; + + /** + * Block type editor only script handles. + * + * @var array + */ + public array $editor_script_handles; + + /** + * Block type front end script handle. + * + * @deprecated WordPress 6.1.0 + */ + public string $script; + + /** + * Block type front end and editor script handles. + * + * @var array + */ + public array $script_handles; + + /** + * Custom CSS selectors for theme.json style generation. + * + * @var array + */ + public array $selectors; + + /** + * Block type editor style handle. + * + * @deprecated WordPress 6.1.0 + */ + public string $editor_style; + + /** + * Block type editor only style handles. + * + * @var array + */ + public array $editor_style_handles; + + /** + * Block type front end style handle. + * + * @deprecated WordPress 6.1.0 + */ + public string $style; + + /** + * Block type front end and editor style handles. + * + * @var array + */ + public array $style_handles; + + /** + * Block type front end only script handle. + * + * @deprecated WordPress 6.1.0 + */ + public string $view_script; + + /** + * Block type front end only script handles. + * + * @var array + */ + public array $view_script_handles; + + /** + * Block variations. + * + * @var array> + */ + public array $variations; +} diff --git a/vendor/johnbillion/args/src/WP_Comment_Query.php b/vendor/johnbillion/args/src/WP_Comment_Query.php new file mode 100644 index 0000000..27ba2e8 --- /dev/null +++ b/vendor/johnbillion/args/src/WP_Comment_Query.php @@ -0,0 +1,385 @@ + + */ + public array $author__in; + + /** + * Array of author IDs to exclude comments for. + * + * Default empty. + * + * @var array + */ + public array $author__not_in; + + /** + * Array of comment IDs to include. + * + * Default empty. + * + * @var array + */ + public array $comment__in; + + /** + * Array of comment IDs to exclude. + * + * Default empty. + * + * @var array + */ + public array $comment__not_in; + + /** + * Whether to return a comment count (true) or array of comment objects (false). + * + * Default false. + */ + public bool $count; + + /** + * Comment fields to return. Accepts 'ids' for comment IDs only or empty for all fields. + * + * Default empty. + * + * @phpstan-var self::FIELD_* + */ + public string $fields; + + /** + * Array of IDs or email addresses of users whose unapproved comments will be returned by the query regardless of `$status`. + * + * Default empty. + * + * @var array + */ + public array $include_unapproved; + + /** + * Karma score to retrieve matching comments for. + * + * Default empty. + */ + public int $karma; + + /** + * Maximum number of comments to retrieve. + * + * Default empty (no limit). + */ + public int $number; + + /** + * When used with `$number`, defines the page of results to return. When used with `$offset`, `$offset` takes precedence. + * + * Default 1. + */ + public int $paged; + + /** + * Number of comments to offset the query. Used to build `LIMIT` clause. + * + * Default 0. + */ + public int $offset; + + /** + * Whether to disable the `SQL_CALC_FOUND_ROWS` query. + * + * Default: true. + */ + public bool $no_found_rows; + + /** + * Field(s) to order comments by. To use 'meta_value' or 'meta_value_num', `$meta_key` must also be defined. To sort by a specific `$meta_query` clause, use that clause's array key. Accepts: + * + * - 'comment_agent' + * - 'comment_approved' + * - 'comment_author' + * - 'comment_author_email' + * - 'comment_author_IP' + * - 'comment_author_url' + * - 'comment_content' + * - 'comment_date' + * - 'comment_date_gmt' + * - 'comment_ID' + * - 'comment_karma' + * - 'comment_parent' + * - 'comment_post_ID' + * - 'comment_type' + * - 'user_id' + * - 'comment__in' + * - 'meta_value' + * - 'meta_value_num' + * - the value of `$meta_key` + * - the array keys of `$meta_query` + * - an empty array or 'none' to disable `ORDER BY` clause. + * + * Default: 'comment_date_gmt'. + * + * @var string|array + */ + public $orderby; + + /** + * How to order retrieved comments. Accepts 'ASC', 'DESC'. + * + * Default: 'DESC'. + * + * @phpstan-var Shared\Base::ORDER_* + */ + public string $order; + + /** + * Parent ID of comment to retrieve children of. + * + * Default empty. + */ + public int $parent; + + /** + * Array of parent IDs of comments to retrieve children for. + * + * Default empty. + * + * @var array + */ + public array $parent__in; + + /** + * Array of parent IDs of comments *not* to retrieve children for. + * + * Default empty. + * + * @var array + */ + public array $parent__not_in; + + /** + * Array of author IDs to retrieve comments for. + * + * Default empty. + * + * @var array + */ + public array $post_author__in; + + /** + * Array of author IDs *not* to retrieve comments for. + * + * Default empty. + * + * @var array + */ + public array $post_author__not_in; + + /** + * Limit results to those affiliated with a given post ID. + * + * Default 0. + */ + public int $post_id; + + /** + * Array of post IDs to include affiliated comments for. + * + * Default empty. + * + * @var array + */ + public array $post__in; + + /** + * Array of post IDs to exclude affiliated comments for. + * + * Default empty. + * + * @var array + */ + public array $post__not_in; + + /** + * Post author ID to limit results by. + * + * Default empty. + */ + public int $post_author; + + /** + * Post status or array of post statuses to retrieve affiliated comments for. Pass 'any' to match any value. + * + * Default empty. + * + * @var string|array + */ + public $post_status; + + /** + * Post type or array of post types to retrieve affiliated comments for. Pass 'any' to match any value. + * + * Default empty. + * + * @var string|array + */ + public $post_type; + + /** + * Post name to retrieve affiliated comments for. + * + * Default empty. + */ + public string $post_name; + + /** + * Post parent ID to retrieve affiliated comments for. + * + * Default empty. + */ + public int $post_parent; + + /** + * Search term(s) to retrieve matching comments for. + * + * Default empty. + */ + public string $search; + + /** + * Comment statuses to limit results by. Accepts an array or space/comma-separated list of: + * + * - 'hold' (`comment_status=0`) + * - 'approve' (`comment_status=1`) + * - 'all' + * - a custom comment status + * + * Default 'all'. + * + * @var string|array + */ + public $status; + + /** + * Include comments of a given type, or array of types. Accepts: + * + * - 'comment' + * - 'pings' (includes 'pingback' and 'trackback') + * - any custom type string + * + * Default empty. + * + * @var string|array + */ + public $type; + + /** + * Include comments from a given array of comment types. + * + * Default empty. + * + * @var array + */ + public array $type__in; + + /** + * Exclude comments from a given array of comment types. + * + * Default empty. + * + * @var array + */ + public array $type__not_in; + + /** + * Include comments for a specific user ID. + * + * Default empty. + */ + public int $user_id; + + /** + * Whether to include comment descendants in the results. + * + * - 'threaded' returns a tree, with each comment's children stored in a `children` property on the `WP_Comment` object. + * - 'flat' returns a flat array of found comments plus their children. + * - Boolean `false` leaves out descendants. + * + * The parameter is ignored (forced to `false`) when `$fields` is 'ids' or 'counts'. + * + * Default: false. + * + * @var false|string + * @phpstan-var self::HIERARCHICAL_* + */ + public $hierarchical; + + /** + * Unique cache key to be produced when this query is stored in an object cache. + * + * Default is 'core'. + */ + public string $cache_domain; + + /** + * Whether to prime the metadata cache for found comments. + * + * Default true. + */ + public bool $update_comment_meta_cache; + + /** + * Whether to prime the cache for comment posts. + * + * Default false. + */ + public bool $update_comment_post_cache; + + /** + * Currently unused. + */ + protected int $ID; + + /** + * Currently unused. + */ + protected int $post_ID; +} diff --git a/vendor/johnbillion/args/src/WP_Customize_Control.php b/vendor/johnbillion/args/src/WP_Customize_Control.php new file mode 100644 index 0000000..2a3b0a1 --- /dev/null +++ b/vendor/johnbillion/args/src/WP_Customize_Control.php @@ -0,0 +1,130 @@ + + */ + public array $settings; + + /** + * The primary setting for the control (if there is one). + * + * Default 'default'. + */ + public string $setting; + + /** + * Capability required to use this control. + * + * Normally this is empty and the capability is derived from `$settings`. + */ + public string $capability; + + /** + * Order priority to load the control. + * + * Default 10. + */ + public int $priority; + + /** + * Section the control belongs to. + * + * Default empty. + */ + public string $section; + + /** + * Label for the control. + * + * Default empty. + */ + public string $label; + + /** + * Description for the control. + * + * Default empty. + */ + public string $description; + + /** + * List of choices for 'radio' or 'select' type controls, where values are the keys, and labels are the values. + * + * Default empty array. + * + * @var array + */ + public array $choices; + + /** + * List of custom input attributes for control output, where attribute names are the keys and values are the values. + * + * Not used for 'checkbox', 'radio', 'select', 'textarea', or 'dropdown-pages' control types. + * + * Default empty array. + * + * @var array + */ + public array $input_attrs; + + /** + * Show UI for adding new content, currently only used for the dropdown-pages control. + * + * Default false. + */ + public bool $allow_addition; + + /** + * Deprecated. Use `WP_Customize_Control::json()` instead. + * + * @var array + */ + protected array $json; + + /** + * Control type. + * + * Core controls include 'text', 'checkbox', 'textarea', 'radio', 'select', and 'dropdown-pages'. Additional input types such as 'email', 'url', 'number', 'hidden', and 'date' are supported implicitly. + * + * Default 'text'. + */ + public string $type; + + /** + * Active callback. + * + * @var callable + * @phpstan-var callable(\WP_Customize_Control): bool + */ + public $active_callback; +} diff --git a/vendor/johnbillion/args/src/WP_Customize_Manager.php b/vendor/johnbillion/args/src/WP_Customize_Manager.php new file mode 100644 index 0000000..abee7ed --- /dev/null +++ b/vendor/johnbillion/args/src/WP_Customize_Manager.php @@ -0,0 +1,57 @@ + + * @phpstan-var string|array{ + * 0: string, + * } + */ + public $theme_supports; + + /** + * Title of the panel to show in UI. + */ + public string $title; + + /** + * Description to show in the UI. + */ + public string $description; + + /** + * Type of the panel. + */ + public string $type; + + /** + * Active callback. + * + * @phpstan-var callable(\WP_Customize_Panel): bool + */ + public $active_callback; +} diff --git a/vendor/johnbillion/args/src/WP_Customize_Section.php b/vendor/johnbillion/args/src/WP_Customize_Section.php new file mode 100644 index 0000000..d0281fb --- /dev/null +++ b/vendor/johnbillion/args/src/WP_Customize_Section.php @@ -0,0 +1,73 @@ + + * @phpstan-var string|array{ + * 0: string, + * } + */ + public $theme_supports; + + /** + * Title of the section to show in UI. + */ + public string $title; + + /** + * Description to show in the UI. + */ + public string $description; + + /** + * Type of the section. + */ + public string $type; + + /** + * Active callback. + * + * @phpstan-var callable(\WP_Customize_Section): bool + */ + public $active_callback; + + /** + * Hide the description behind a help icon, instead of inline above the first control. + * + * Default false. + */ + public bool $description_hidden; +} diff --git a/vendor/johnbillion/args/src/WP_Customize_Setting.php b/vendor/johnbillion/args/src/WP_Customize_Setting.php new file mode 100644 index 0000000..74b8ea1 --- /dev/null +++ b/vendor/johnbillion/args/src/WP_Customize_Setting.php @@ -0,0 +1,87 @@ + + * @phpstan-var string|array{ + * 0: string, + * } + */ + public $theme_supports; + + /** + * Default value for the setting. + * + * Default is empty string. + */ + public string $default; + + /** + * Options for rendering the live preview of changes in Customizer. + * + * Using 'refresh' makes the change visible by reloading the whole preview. Using 'postMessage' allows a custom JavaScript to handle live changes. + * + * Default is 'refresh'. + * + * @phpstan-var self::TRANSPORT_* + */ + public string $transport; + + /** + * Server-side validation callback for the setting's value. + * + * @var callable + * @phpstan-var callable(\WP_Error, mixed, \WP_Customize_Setting): \WP_Error + */ + public $validate_callback; + + /** + * Callback to filter a Customize setting value in un-slashed form. + * + * @var callable + * @phpstan-var callable(mixed, \WP_Customize_Setting): mixed + */ + public $sanitize_callback; + + /** + * Callback to convert a Customize PHP setting value to a value that is JSON serializable. + * + * @var callable + * @phpstan-var callable(mixed, \WP_Customize_Setting): mixed + */ + public $sanitize_js_callback; + + /** + * Whether or not the setting is initially dirty when created. + */ + public bool $dirty; +} diff --git a/vendor/johnbillion/args/src/WP_Http.php b/vendor/johnbillion/args/src/WP_Http.php new file mode 100644 index 0000000..38c4dc3 --- /dev/null +++ b/vendor/johnbillion/args/src/WP_Http.php @@ -0,0 +1,165 @@ + */ + protected array $map = [ + 'user_agent' => 'user-agent', + ]; + + /** + * Request method. + * + * Some transports technically allow others, but should not be assumed. + * + * Default 'GET'. + * + * @phpstan-var self::METHOD_* + */ + public string $method; + + /** + * How long the connection should stay open in seconds. + * + * Default 5. + */ + public float $timeout; + + /** + * Number of allowed redirects. Not supported by all transports. + * + * Default 5. + */ + public int $redirection; + + /** + * Version of the HTTP protocol to use. + * + * Accepts '1.0' and '1.1'. + * + * Default '1.0'. + */ + public string $httpversion; + + /** + * User-agent value sent. + * + * Default `'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' )`. + */ + public string $user_agent; + + /** + * Whether to pass URLs through `wp_http_validate_url()`. + * + * Default false. + */ + public bool $reject_unsafe_urls; + + /** + * Whether the calling code requires the result of the request. + * + * If set to false, the request will be sent to the remote server, and processing returned to the calling code immediately, the caller will know if the request succeeded or failed, but will not receive any response from the remote server. + * + * Default true. + */ + public bool $blocking; + + /** + * Array of headers to send with the request. + * + * Default empty array. + * + * @var array + */ + public array $headers; + + /** + * List of cookies to send with the request. + * + * Default empty array. + * + * @var string[]|WP_Http_Cookie[] + */ + public array $cookies; + + /** + * Body to send with the request. + * + * Default null. + * + * @var string|mixed[] + */ + public $body; + + /** + * Whether to compress the $body when sending the request. + * + * Default false. + */ + public bool $compress; + + /** + * Whether to decompress a compressed response. + * + * If set to false and compressed content is returned in the response anyway, it will need to be separately decompressed. + * + * Default true. + */ + public bool $decompress; + + /** + * Whether to verify SSL for the request. + * + * Default true. + */ + public bool $sslverify; + + /** + * Absolute path to an SSL certificate `.crt` file. + * + * Default `ABSPATH . WPINC . '/certificates/ca-bundle.crt'`. + */ + public string $sslcertificates; + + /** + * Whether to stream to a file. + * + * If set to true and no filename was given, it will be dropped it in the WP temp dir and its name will be set using the basename of the URL. + * + * Default false. + */ + public bool $stream; + + /** + * Filename of the file to write to when streaming. `$stream` must be set to true. + * + * Default null. + */ + public string $filename; + + /** + * Size in bytes to limit the response to. + * + * Default null. + */ + public int $limit_response_size; +} diff --git a/vendor/johnbillion/args/src/WP_Query.php b/vendor/johnbillion/args/src/WP_Query.php new file mode 100644 index 0000000..61873b4 --- /dev/null +++ b/vendor/johnbillion/args/src/WP_Query.php @@ -0,0 +1,527 @@ +parent'; + + public const PERM_READABLE = 'readable'; + public const PERM_EDITABLE = 'editable'; + + public const COMMENT_COUNT_COMPARE_EQUALS = '='; + public const COMMENT_COUNT_COMPARE_NOT_EQUALS = '!='; + public const COMMENT_COUNT_COMPARE_GREATER_THAN = '>'; + public const COMMENT_COUNT_COMPARE_GREATER_THAN_OR_EQUALS = '>='; + public const COMMENT_COUNT_COMPARE_LESS_THAN = '<'; + public const COMMENT_COUNT_COMPARE_LESS_THAN_OR_EQUALS = '<='; + + use DateQuery\ProvidesArgs; + use MetaQuery\ProvidesArgs; + use TaxQuery\ProvidesArgs; + + /** + * Attachment post ID. Used for 'attachment' post_type. + */ + public int $attachment_id; + + /** + * Author ID, or comma-separated list of IDs. + * + * @var int|string + */ + public $author; + + /** + * User 'user_nicename'. + */ + public string $author_name; + + /** + * An array of author IDs to query from. + * + * @var array + */ + public array $author__in; + + /** + * An array of author IDs not to query from. + * + * @var array + */ + public array $author__not_in; + + /** + * Whether to cache post information. + * + * Default true. + */ + public bool $cache_results; + + /** + * Category ID or comma-separated list of IDs (this or any children). + * + * @var int|string + */ + public $cat; + + /** + * An array of category IDs (AND in). + * + * @var array + */ + public array $category__and; + + /** + * An array of category IDs (OR in, no children). + * + * @var array + */ + public array $category__in; + + /** + * An array of category IDs (NOT in). + * + * @var array + */ + public array $category__not_in; + + /** + * Use category slug (not name, this or any children). + */ + public string $category_name; + + /** + * Filter results by comment count. + * + * Provide an integer to match comment count exactly. Provide an array with integer 'value' and 'compare' operator ('=', '!=', '>', '>=', '<', '<=' ) to compare against comment_count in a specific way. + * + * @var array|int + * @phpstan-var array{ + * value: int, + * compare: self::COMMENT_COUNT_COMPARE_*, + * }|int + */ + public $comment_count; + + /** + * Comment status. + * + * @phpstan-var self::COMMENT_STATUS_* + */ + public string $comment_status; + + /** + * The number of comments to return per page. + * + * Default 'comments_per_page' option. + */ + public int $comments_per_page; + + /** + * Day of the month. + * + * Default empty. Accepts numbers 1-31. + * + * @phpstan-var int<1, 31> + */ + public int $day; + + /** + * Whether to search by exact keyword. + * + * Default false. + */ + public bool $exact; + + /** + * Post fields to query for. + * + * Accepts: + * + * - '' Returns an array of complete post objects (`WP_Post[]`). + * - 'ids' Returns an array of post IDs (`int[]`). + * - 'id=>parent' Returns an associative array of parent post IDs, keyed by post ID (`int[]`). + * + * Default ''. + * + * @phpstan-var self::FIELD_* + */ + public string $fields; + + /** + * Hour of the day. + * + * Default empty. Accepts numbers 0-23. + * + * @phpstan-var int<0, 23> + */ + public int $hour; + + /** + * Whether to ignore sticky posts or not. Setting this to false excludes stickies from 'post__in'. + * + * Default false. + */ + public bool $ignore_sticky_posts; + + /** + * Combination YearMonth. Accepts any four-digit year and month numbers 1-12. + * + * Default empty. + */ + public int $m; + + /** + * The menu order of the posts. + */ + public int $menu_order; + + /** + * Second of the minute. + * + * Default empty. Accepts numbers 0-59. + * + * @phpstan-var int<0, 59> + */ + public int $minute; + + /** + * The two-digit month. + * + * Default empty. Accepts numbers 1-12. + * + * @phpstan-var int<1, 12> + */ + public int $monthnum; + + /** + * Post slug. + */ + public string $name; + + /** + * Show all posts (true) or paginate (false). + * + * Default false. + */ + public bool $nopaging; + + /** + * Whether to skip counting the total rows found. Enabling can improve performance. + * + * Default false. + */ + public bool $no_found_rows; + + /** + * The number of posts to offset before retrieval. + */ + public int $offset; + + /** + * Designates ascending or descending order of posts. + * + * Default 'DESC'. Accepts 'ASC', 'DESC'. + * + * @phpstan-var Shared\Base::ORDER_* + */ + public string $order; + + /** + * Sort retrieved posts by parameter. One or more options may be passed. + * + * To use 'meta_value', or 'meta_value_num', 'meta_key=keyname' must be also be defined. To sort by a specific `$meta_query` clause, use that clause's array key. + * + * Accepts: + * + * - 'none' + * - 'name' + * - 'author' + * - 'date' + * - 'title' + * - 'modified' + * - 'menu_order' + * - 'parent' + * - 'ID' + * - 'rand' + * - 'relevance' + * - 'RAND(x)' (where 'x' is an integer seed value) + * - 'comment_count' + * - 'meta_value' + * - 'meta_value_num' + * - 'post__in' + * - 'post_name__in' + * - 'post_parent__in' + * - The array keys of `$meta_query` + * + * Default is 'date', except when a search is being performed, when the default is 'relevance'. + * + * @var string|array + */ + public $orderby; + + /** + * Post ID. + */ + public int $p; + + /** + * Show the number of posts that would show up on page X of a static front page. + */ + public int $page; + + /** + * The number of the current page. + */ + public int $paged; + + /** + * Page ID. + */ + public int $page_id; + + /** + * Page slug. + */ + public string $pagename; + + /** + * Show posts if user has the appropriate capability. + * + * @phpstan-var self::PERM_* + */ + public string $perm; + + /** + * Ping status. + * + * @phpstan-var self::COMMENT_STATUS_* + */ + public string $ping_status; + + /** + * An array of post IDs to retrieve, sticky posts will be included. + * + * @var array + */ + public array $post__in; + + /** + * An array of post IDs not to retrieve. + * + * @var array + */ + public array $post__not_in; + + /** + * The mime type of the post. Used for 'attachment' post_type. + */ + public string $post_mime_type; + + /** + * An array of post slugs that results must match. + * + * @var array + */ + public array $post_name__in; + + /** + * Page ID to retrieve child pages for. Use 0 to only retrieve top-level pages. + */ + public int $post_parent; + + /** + * An array containing parent page IDs to query child pages from. + * + * @var array + */ + public array $post_parent__in; + + /** + * An array containing parent page IDs not to query child pages from. + * + * @var array + */ + public array $post_parent__not_in; + + /** + * A post type slug (string) or array of post type slugs. + * + * Default 'any' if using 'tax_query'. + * + * @var string|array + */ + public $post_type; + + /** + * A post status (string) or array of post statuses. + * + * @var string|array + */ + public $post_status; + + /** + * The number of posts to query for. Use -1 to request all posts. + * + * @phpstan-var positive-int | -1 + */ + public int $posts_per_page; + + /** + * The number of posts to query for by archive page. Overrides 'posts_per_page' when is_archive(), or is_search() are true. + * + * @phpstan-var positive-int | -1 + */ + public int $posts_per_archive_page; + + /** + * Search keyword(s). + * + * Prepending a term with a hyphen will exclude posts matching that term. Eg, 'pillow -sofa' will return posts containing 'pillow' but not 'sofa'. + * + * The character used for exclusion can be modified using the the 'wp_query_search_exclusion_prefix' filter. + */ + public string $s; + + /** + * Array of column names to be searched. + * + * Accepts 'post_title', 'post_excerpt', and 'post_content'. + * + * Default empty array. + * + * @var array + * @phpstan-var list<'post_title'|'post_excerpt'|'post_content'> + */ + public array $search_columns; + + /** + * Second of the minute. + * + * Default empty. Accepts numbers 0-59. + * + * @phpstan-var int<0, 59> + */ + public int $second; + + /** + * Whether to search by phrase. + * + * Default false. + */ + public bool $sentence; + + /** + * Whether to suppress filters. + * + * Default false. + */ + public bool $suppress_filters; + + /** + * Tag slug. Comma-separated (either), Plus-separated (all). + */ + public string $tag; + + /** + * An array of tag IDs (AND in). + * + * @var array + */ + public array $tag__and; + + /** + * An array of tag IDs (OR in). + * + * @var array + */ + public array $tag__in; + + /** + * An array of tag IDs (NOT in). + * + * @var array + */ + public array $tag__not_in; + + /** + * Tag id or comma-separated list of IDs. + */ + public int $tag_id; + + /** + * An array of tag slugs (AND in). + * + * @var array + */ + public array $tag_slug__and; + + /** + * An array of tag slugs (OR in). unless 'ignore_sticky_posts' is true. + * + * @var array + */ + public array $tag_slug__in; + + /** + * Post title. + */ + public string $title; + + /** + * Whether to update the post meta cache. + * + * Default true. + */ + public bool $update_post_meta_cache; + + /** + * Whether to update the post term cache. + * + * Default true. + */ + public bool $update_post_term_cache; + + /** + * Whether to update the menu item cache. + * + * Default true. + */ + public bool $update_menu_item_cache; + + /** + * Whether to lazy-load term meta. Setting to false will disable cache priming for term meta, so that each get_term_meta() call will hit the database. + * + * Defaults to the value of `$update_post_term_cache`. + */ + public bool $lazy_load_term_meta; + + /** + * The week number of the year. + * + * Default empty. Accepts numbers 0-53. + * + * @phpstan-var int<0, 53> + */ + public int $w; + + /** + * The four-digit year. + * + * Default empty. Accepts any four-digit year. + */ + public int $year; + +} diff --git a/vendor/johnbillion/args/src/WP_Term_Query.php b/vendor/johnbillion/args/src/WP_Term_Query.php new file mode 100644 index 0000000..199db8b --- /dev/null +++ b/vendor/johnbillion/args/src/WP_Term_Query.php @@ -0,0 +1,245 @@ +parent'; + public const FIELD_ID_NAME = 'id=>name'; + public const FIELD_ID_SLUG = 'id=>slug'; + + use MetaQuery\ProvidesArgs; + + /** + * Taxonomy name, or array of taxonomies, to which results should be limited. + * + * @var string|array + */ + public $taxonomy; + + /** + * Object ID, or array of object IDs. Results will be limited to terms associated with these objects. + * + * @var int|array + */ + public $object_ids; + + /** + * Field(s) to order terms by. + * + * Accepts: + * + * - Term fields ('name', 'slug', 'term_group', 'term_id', 'id', + * 'description', 'parent', 'term_order'). Unless `$object_ids` + * is not empty, 'term_order' is treated the same as 'term_id'. + * - 'count' to use the number of objects associated with the term. + * - 'include' to match the 'order' of the $include param. + * - 'slug__in' to match the 'order' of the $slug param. + * - 'meta_value', 'meta_value_num'. + * - The value of `$meta_key`. + * - The array keys of `$meta_query`. + * - 'none' to omit the ORDER BY clause. + * + * Default 'name'. + */ + public string $orderby; + + /** + * Whether to order terms in ascending or descending order. Accepts 'ASC' (ascending) or 'DESC' (descending). + * + * Default 'ASC'. + * + * @phpstan-var Shared\Base::ORDER_* + */ + public string $order; + + /** + * Whether to hide terms not assigned to any posts. + * + * Default true. + */ + public bool $hide_empty; + + /** + * Array of term IDs to include. + * + * @var array + */ + public array $include; + + /** + * Array of term IDs to exclude. If `$include` is non-empty, `$exclude` is ignored. + * + * @var array + */ + public array $exclude; + + /** + * Array of term IDs to exclude along with all of their descendant terms. If `$include` is non-empty, `$exclude_tree` is ignored. + * + * @var array + */ + public array $exclude_tree; + + /** + * Maximum number of terms to return. Accepts 0 (all) or any positive number. + * + * Note that `$number` may not return accurate results when coupled with `$object_ids`. See #41796 for details. + * + * Default 0 (all). + * + * @phpstan-var positive-int | 0 + */ + public int $number; + + /** + * The number by which to offset the terms query. + * + * @phpstan-var positive-int | 0 + */ + public int $offset; + + /** + * Term fields to query for. + * + * Accepts: + * + * - 'all' Returns an array of complete term objects (`WP_Term[]`). + * - 'all_with_object_id' Returns an array of term objects + * with the 'object_id' param (`WP_Term[]`). Works only + * when the `$object_ids` parameter is populated. + * - 'ids' Returns an array of term IDs (`int[]`). + * - 'tt_ids' Returns an array of term taxonomy IDs (`int[]`). + * - 'names' Returns an array of term names (`string[]`). + * - 'slugs' Returns an array of term slugs (`string[]`). + * - 'count' Returns the number of matching terms (`int`). + * - 'id=>parent' Returns an associative array of parent term IDs, + * keyed by term ID (`int[]`). + * - 'id=>name' Returns an associative array of term names, + * keyed by term ID (`string[]`). + * - 'id=>slug' Returns an associative array of term slugs, + * keyed by term ID (`string[]`). + * + * Default 'all'. + * + * @phpstan-var self::FIELD_* + */ + public string $fields; + + /** + * Whether to return a term count. If true, will take precedence over `$fields`. + * + * Default false. + */ + public bool $count; + + /** + * Name or array of names to return term(s) for. + * + * @var string|array + */ + public $name; + + /** + * Slug or array of slugs to return term(s) for. + * + * @var string|array + */ + public $slug; + + /** + * Term taxonomy ID, or array of term taxonomy IDs, to match when querying terms. + * + * @var int|array + */ + public $term_taxonomy_id; + + /** + * Whether to include terms that have non-empty descendants (even if `$hide_empty` is set to true). + * + * Default true. + */ + public bool $hierarchical; + + /** + * Search criteria to match terms. Will be SQL-formatted with wildcards before and after. + */ + public string $search; + + /** + * Retrieve terms with criteria by which a term is LIKE `$name__like`. + */ + public string $name__like; + + /** + * Retrieve terms where the description is LIKE `$description__like`. + */ + public string $description__like; + + /** + * Whether to pad the quantity of a term's children in the quantity of each term's "count" object variable. + * + * Default false. + */ + public bool $pad_counts; + + /** + * Whether to return terms regardless of ancestry or whether the terms are empty. Accepts 'all' or empty (disabled). + * + * Default ''. + * + * @phpstan-var 'all' | '' + */ + public string $get; + + /** + * Term ID to retrieve child terms of. If multiple taxonomies are passed, `$child_of` is ignored. + */ + public int $child_of; + + /** + * Parent term ID to retrieve direct-child terms of. + */ + public int $parent; + + /** + * True to limit results to terms that have no children. This parameter has no effect on non-hierarchical taxonomies. + * + * Default false. + */ + public bool $childless; + + /** + * Unique cache key to be produced when this query is stored in an object cache. + * + * Default 'core'. + */ + public string $cache_domain; + + /** + * Whether to cache term information. + * + * Default true. + */ + public bool $cache_results; + + /** + * Whether to prime meta caches for matched terms. + * + * Default true. + */ + public bool $update_term_meta_cache; +} diff --git a/vendor/johnbillion/args/src/WP_User_Query.php b/vendor/johnbillion/args/src/WP_User_Query.php new file mode 100644 index 0000000..281045c --- /dev/null +++ b/vendor/johnbillion/args/src/WP_User_Query.php @@ -0,0 +1,292 @@ + + */ + public $role; + + /** + * An array of role names. Matched users must have at least one of these roles. + * + * Default empty array. + * + * @var array + */ + public array $role__in; + + /** + * An array of role names to exclude. Users matching one or more of these roles will not be included in results. + * + * Default empty array. + * + * @var array + */ + public array $role__not_in; + + /** + * An array of user IDs to include. + * + * Default empty array. + * + * @var array + */ + public array $include; + + /** + * An array of user IDs to exclude. + * + * Default empty array. + * + * @var array + */ + public array $exclude; + + /** + * Search keyword. Searches for possible string matches on columns. When `$search_columns` is left empty, it tries to determine which column to search in based on search string. + * + * Default empty. + */ + public string $search; + + /** + * Array of column names to be searched. Accepts 'ID', 'user_login', 'user_email', 'user_url', 'user_nicename', 'display_name'. + * + * Default empty array. + * + * @var array + * @phpstan-var array + */ + public array $search_columns; + + /** + * Field(s) to sort the retrieved users by. + * + * May be a single value, an array of values, or a multi-dimensional array with fields as keys and orders ('ASC' or 'DESC') as values. Accepted values are: + * + * - 'ID', 'display_name' (or 'name') + * - 'include' + * - 'user_login' (or 'login') + * - 'login__in' + * - 'user_nicename' (or 'nicename') + * - 'nicename__in' + * - 'user_email (or 'email') + * - 'user_url' (or 'url') + * - 'user_registered' (or 'registered') + * - 'post_count' + * - 'meta_value' + * - 'meta_value_num' + * - the value of `$meta_key` + * - or an array key of `$meta_query`. + * + * To use 'meta_value' or 'meta_value_num', `$meta_key` must be also be defined. + * + * Default 'user_login'. + * + * @var string|mixed[] + */ + public $orderby; + + /** + * Designates ascending or descending order of users. Order values passed as part of an `$orderby` array take precedence over this parameter. Accepts 'ASC', 'DESC'. + * + * Default 'ASC'. + * + * @phpstan-var Shared\Base::ORDER_* + */ + public string $order; + + /** + * Number of users to offset in retrieved results. Can be used in conjunction with pagination. + * + * Default 0. + */ + public int $offset; + + /** + * Number of users to limit the query for. Can be used in conjunction with pagination. Value -1 (all) is supported, but should be used with caution on larger sites. + * + * Default -1 (all users). + */ + public int $number; + + /** + * When used with number, defines the page of results to return. + * + * Default 1. + */ + public int $paged; + + /** + * Whether to count the total number of users found. If pagination is not needed, setting this to false can improve performance. + * + * Default true. + */ + public bool $count_total; + + /** + * Which fields to return. Single or all fields (string), or array of fields. + * + * Accepts: + * + * - 'ID' + * - 'display_name' + * - 'user_login' + * - 'user_nicename' + * - 'user_email' + * - 'user_url' + * - 'user_registered' + * + * Use 'all' for all fields and 'all_with_meta' to include meta fields. + * + * Default 'all'. + * + * @var string|array + * @phpstan-var (self::FIELD_*|'all'|'all_with_meta')|array + */ + public $fields; + + /** + * Type of users to query. Accepts 'authors'. + * + * Default empty (all users). + * + * @phpstan-var self::WHO_* + */ + public string $who; + + /** + * Pass an array of post types to filter results to users who have published posts in those post types. + * + * `true` is an alias for all public post types. + * + * @var true|array + */ + public $has_published_posts; + + /** + * The user nicename. + * + * Default empty. + */ + public string $nicename; + + /** + * An array of nicenames to include. Users matching one of these nicenames will be included in results. + * + * Default empty array. + * + * @var array + */ + public array $nicename__in; + + /** + * An array of nicenames to exclude. Users matching one of these nicenames will not be included in results. + * + * Default empty array. + * + * @var array + */ + public array $nicename__not_in; + + /** + * The user login. + * + * Default empty. + */ + public string $login; + + /** + * An array of logins to include. Users matching one of these logins will be included in results. + * + * Default empty array. + * + * @var array + */ + public array $login__in; + + /** + * An array of logins to exclude. Users matching one of these logins will not be included in results. + * + * Default empty array. + * + * @var array + */ + public array $login__not_in; + + /** + * Whether to cache user information. + * + * Default true. + */ + public bool $cache_results; + + /** + * An array or a comma-separated list of capability names that users must match to be included in results. + * + * Note that this is an inclusive list: users must match *each* capability. + * + * Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}. + * + * @var string|array + */ + public $capability; + + /** + * An array of capability names. Matched users must have at least one of these capabilities. + * + * Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}. + * + * @var array + */ + public array $capability__in; + + /** + * An array of capability names to exclude. Users matching one or more of these capabilities will not be included in results. + * + * Does NOT work for capabilities not in the database or filtered via {@see 'map_meta_cap'}. + * + * @var array + */ + public array $capability__not_in; +} diff --git a/vendor/johnbillion/args/src/get_categories.php b/vendor/johnbillion/args/src/get_categories.php new file mode 100644 index 0000000..6f91b34 --- /dev/null +++ b/vendor/johnbillion/args/src/get_categories.php @@ -0,0 +1,13 @@ + + */ + public array $include; + + /** + * An array of post IDs not to retrieve. + * + * Default empty array. + * + * @var array + */ + public array $exclude; + + /** + * Whether to suppress filters. + * + * Default true. + */ + public bool $suppress_filters; + +} diff --git a/vendor/johnbillion/args/src/get_tags.php b/vendor/johnbillion/args/src/get_tags.php new file mode 100644 index 0000000..87dacec --- /dev/null +++ b/vendor/johnbillion/args/src/get_tags.php @@ -0,0 +1,13 @@ + + * @phpstan-var bool|array{ + * schema: array, + * prepare_callback: callable(mixed,\WP_REST_Request,array): mixed, + * } + */ + public $show_in_rest; + + /** + * Whether to enable revisions support for this meta_key. + * + * Can only be used when the object type is 'post'. + */ + public bool $revisions_enabled; +} diff --git a/vendor/johnbillion/args/src/register_post_meta.php b/vendor/johnbillion/args/src/register_post_meta.php new file mode 100644 index 0000000..78f798c --- /dev/null +++ b/vendor/johnbillion/args/src/register_post_meta.php @@ -0,0 +1,13 @@ + + */ + public array $labels; + + /** + * A short descriptive summary of what the post type is. + * + * Default empty. + */ + public string $description; + + /** + * Whether a post type is intended for use publicly either via the admin interface or by front-end users. + * + * While the default settings of `$exclude_from_search`, `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus` + * are inherited from `$public`, each does not rely on this relationship and controls a very specific intention. + * + * Default false. + */ + public bool $public; + + /** + * Whether the post type is hierarchical (e.g. page). + * + * Default false. + */ + public bool $hierarchical; + + /** + * Whether to exclude posts with this post type from front end search results. + * + * Default is the opposite value of `$public`. + */ + public bool $exclude_from_search; + + /** + * Whether queries can be performed on the front end for the post type as part of `parse_request()`. + * + * Endpoints would include: + * + * - `?post_type={post_type_key}` + * - `?{post_type_key}={single_post_slug}` + * - `?{post_type_query_var}={single_post_slug}` + * + * If not set, the default is inherited from `$public`. + */ + public bool $publicly_queryable; + + /** + * Whether to generate and allow a UI for managing this post type in the admin. + * + * Default is value of `$public`. + */ + public bool $show_ui; + + /** + * Where to show the post type in the admin menu. To work, `$show_ui` must be true. + * + * - If true the post type is shown in its own top level menu. + * - If false, no menu is shown. + * - If a string of an existing top level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post type will be placed as a sub-menu of that. + * + * Default is value of `$show_ui`. + * + * @var bool|string + */ + public $show_in_menu; + + /** + * Makes this post type available for selection in navigation menus. + * + * Default is value of `$public`. + */ + public bool $show_in_nav_menus; + + /** + * Makes this post type available via the admin bar. + * + * Default is value of `$show_in_menu`. + */ + public bool $show_in_admin_bar; + + /** + * Whether to include the post type in the REST API. + * + * Set this to true for the post type to be available in the block editor. + */ + public bool $show_in_rest; + + /** + * To change the base URL of REST API route. + * + * Default is `$post_type`. + */ + public string $rest_base; + + /** + * To change the namespace URL of REST API route. + * + * Default is wp/v2. + */ + public string $rest_namespace; + + /** + * REST API controller class name. + * + * Default is 'WP_REST_Posts_Controller'. + * + * @phpstan-var class-string<\WP_REST_Controller> + */ + public string $rest_controller_class; + + /** + * REST API autosave controller class name. + * + * Default is 'WP_REST_Autosaves_Controller'. + * + * @phpstan-var class-string<\WP_REST_Controller> + */ + public string $autosave_rest_controller_class; + + /** + * REST API revisions controller class name. + * + * Default is 'WP_REST_Revisions_Controller'. + * + * @phpstan-var class-string<\WP_REST_Controller> + */ + public string $revisions_rest_controller_class; + + /** + * A flag to direct the REST API controllers for autosave / revisions should be registered before/after the post type controller. + */ + public bool $late_route_registration; + + /** + * The position in the menu order the post type should appear. To work, `$show_in_menu` must be true. + * + * Default null (at the bottom). + */ + public int $menu_position; + + /** + * The URL to the icon to be used for this menu. + * + * - Pass a base64-encoded SVG using a data URI, which will be colored to match the color scheme -- this should begin with `data:image/svg+xml;base64,`. + * - Pass the name of a Dashicons helper class to use a font icon, e.g. `dashicons-chart-pie`. + * - Pass `'none'` to leave `div.wp-menu-image` empty so an icon can be added via CSS. + * + * Defaults to use the posts icon. + */ + public string $menu_icon; + + /** + * The strings to use to build the read, edit, and delete capabilities. + * + * Passed as an array to allow for alternative plurals when using this argument as a base to construct the capabilities, e.g. array('story', 'stories'). + * + * Default [ 'post', 'posts' ]. + * + * @var array + * @phpstan-var array{ + * 0: string, + * 1: string, + * } + */ + public array $capability_type; + + /** + * Array of capabilities for this post type. + * + * `$capability_type` is used as a base to construct capabilities by default. See `get_post_type_capabilities()`. + * + * @var array + * @phpstan-var array{ + * edit_post?: string, + * read_post?: string, + * delete_post?: string, + * edit_posts?: string, + * edit_others_posts?: string, + * delete_posts?: string, + * publish_posts?: string, + * read_private_posts?: string, + * read?: string, + * delete_private_posts?: string, + * delete_published_posts?: string, + * delete_others_posts?: string, + * edit_private_posts?: string, + * edit_published_posts?: string, + * create_posts?: string, + * } + */ + public array $capabilities; + + /** + * Whether to use the internal default meta capability handling. + * + * Default false. + */ + public bool $map_meta_cap; + + /** + * Core feature(s) the post type supports. Serves as an alias for calling `add_post_type_support()` directly. + * + * Core features include: + * + * - 'title' + * - 'editor' + * - 'comments' + * - 'revisions' + * - 'trackbacks' + * - 'author' + * - 'excerpt' + * - 'page-attributes' + * - 'thumbnail' + * - 'custom-fields' + * - 'post-formats' + * + * Additionally, the 'revisions' feature dictates whether the post type will store revisions, and the 'comments' + * feature dictates whether the comments count will show on the edit screen. A feature can also be specified as + * an array of arguments to provide additional information about supporting that feature. Example: + * + * array( 'my_feature', array( 'field' => 'value' ) ) + * + * Default is an array containing 'title' and 'editor'. + * + * @var array)> + */ + public array $supports; + + /** + * Provide a callback function that sets up the meta boxes for the edit form. + * + * Do `remove_meta_box()` and `add_meta_box()` calls in the callback. + * + * Default null. + * + * @var callable + * @phpstan-var callable(\WP_Post): void + */ + public $register_meta_box_cb; + + /** + * An array of taxonomy identifiers that will be registered for the post type. + * + * Taxonomies can be registered later with `register_taxonomy()` or `register_taxonomy_for_object_type()`. + * + * Default empty array. + * + * @var array + */ + public array $taxonomies; + + /** + * Whether there should be post type archives, or if a string, the archive slug to use. + * + * Will generate the proper rewrite rules if `$rewrite` is enabled. + * + * Default false. + * + * @var bool|string + */ + public $has_archive; + + /** + * Triggers the handling of rewrites for this post type. + * + * To prevent rewrite, set to false. + * + * Defaults to true, using `$post_type` as slug. To specify rewrite rules, + * an array can be passed. + * + * @var bool|array + * @phpstan-var bool|array{ + * slug?: string, + * with_front?: bool, + * feeds?: bool, + * pages?: bool, + * ep_mask?: int, + * } + */ + public $rewrite; + + /** + * Sets the query_var key for this post type. + * + * Defaults to `$post_type` key. + * + * - If false, a post type cannot be loaded at `?{query_var}={post_slug}`. + * - If specified as a string, the query `?{query_var_string}={post_slug}` will be valid. + * + * @var string|bool + */ + public $query_var; + + /** + * Whether to allow this post type to be exported. + * + * Default true. + */ + public bool $can_export; + + /** + * Whether to delete posts of this type when deleting a user. + * + * - If true, posts of this type belonging to the user will be moved to Trash when the user is deleted. + * - If false, posts of this type belonging to the user will *not* be trashed or deleted. + * - If not set (the default), posts are trashed if post type supports the 'author' feature. Otherwise posts are not trashed or deleted. + * + * Default null. + */ + public bool $delete_with_user; + + /** + * Array of blocks to use as the default initial state for an editor session. + * + * Each item should be an array containing block name and optional attributes. + * + * Default empty array. + * + * @var array>> + */ + public array $template; + + /** + * Whether the block template should be locked if `$template` is set. + * + * - If set to 'all', the user is unable to insert new blocks, move existing blocks and delete blocks. + * - If set to 'insert', the user is able to move existing blocks but is unable to insert new blocks and delete blocks + * + * Default false. + * + * @var string|false + * @phpstan-var self::TEMPLATE_LOCK_* + */ + public $template_lock; + + /** + * FOR INTERNAL USE ONLY! True if this post type is a native or "built-in" post_type. + * + * Default false. + */ + protected bool $_builtin; + + /** + * FOR INTERNAL USE ONLY! URL segment to use for edit link of this post type. + * + * Default 'post.php?post=%d'. + */ + protected string $_edit_link; +} diff --git a/vendor/johnbillion/args/src/register_rest_field.php b/vendor/johnbillion/args/src/register_rest_field.php new file mode 100644 index 0000000..2817035 --- /dev/null +++ b/vendor/johnbillion/args/src/register_rest_field.php @@ -0,0 +1,41 @@ + + */ + public array $labels; + + /** + * A short descriptive summary of what the taxonomy is for. + * + * Default empty. + */ + public string $description; + + /** + * Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users. The default settings of `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus` are inherited from `$public`. + */ + public bool $public; + + /** + * Whether the taxonomy is publicly queryable. If not set, the default is inherited from `$public` + */ + public bool $publicly_queryable; + + /** + * Whether the taxonomy is hierarchical. + * + * Default false. + */ + public bool $hierarchical; + + /** + * Whether to generate and allow a UI for managing terms in this taxonomy in the admin. If not set, the default is inherited from `$public` (default true). + */ + public bool $show_ui; + + /** + * Whether to show the taxonomy in the admin menu. If true, the taxonomy is shown as a submenu of the object type menu. If false, no menu is shown. `$show_ui` must be true. If not set, default is inherited from `$show_ui` (default true). + */ + public bool $show_in_menu; + + /** + * Makes this taxonomy available for selection in navigation menus. If not set, the default is inherited from `$public` (default true). + */ + public bool $show_in_nav_menus; + + /** + * Whether to include the taxonomy in the REST API. Set this to true for the taxonomy to be available in the block editor. + */ + public bool $show_in_rest; + + /** + * To change the base url of REST API route. + * + * Default is `$taxonomy`. + */ + public string $rest_base; + + /** + * To change the namespace URL of REST API route. + * + * Default is wp/v2. + */ + public string $rest_namespace; + + /** + * REST API Controller class name. + * + * Default is 'WP_REST_Terms_Controller'. + * + * @phpstan-var class-string<\WP_REST_Controller> + */ + public string $rest_controller_class; + + /** + * Whether to list the taxonomy in the Tag Cloud Widget controls. If not set, the default is inherited from `$show_ui` (default true). + */ + public bool $show_tagcloud; + + /** + * Whether to show the taxonomy in the quick/bulk edit panel. It not set, the default is inherited from `$show_ui` (default true). + */ + public bool $show_in_quick_edit; + + /** + * Whether to display a column for the taxonomy on its post type listing screens. + * + * Default false. + */ + public bool $show_admin_column; + + /** + * Provide a callback function for the meta box display. If not set, `post_categories_meta_box()` is used for hierarchical taxonomies, and `post_tags_meta_box()` is used for non-hierarchical. If false, no meta box is shown. + * + * @var false|callable + * @phpstan-var false|callable(\WP_Post,mixed[]): void + */ + public $meta_box_cb; + + /** + * Callback function for sanitizing taxonomy data saved from a meta box. If no callback is defined, an appropriate one is determined based on the value of `$meta_box_cb`. + * + * @var callable + * @phpstan-var callable(string,mixed): (int|string)[] + */ + public $meta_box_sanitize_cb; + + /** + * Array of capabilities for this taxonomy. + * + * @var array + * @phpstan-var array{ + * manage_terms: string, + * edit_terms: string, + * delete_terms: string, + * assign_terms: string, + * } + */ + public array $capabilities; + + /** + * Triggers the handling of rewrites for this taxonomy. + * + * Default true, using `$taxonomy` as slug. + * + * To prevent rewrite, set to false. To specify rewrite rules, an array can be passed with any of these keys: + * + * @var bool|array + * @phpstan-var bool|array{ + * slug?: string, + * with_front?: bool, + * hierarchical?: bool, + * ep_mask?: int, + * } + */ + public $rewrite; + + /** + * Sets the query var key for this taxonomy. + * + * Default `$taxonomy` key. If false, a taxonomy cannot be loaded at `?{query_var}={term_slug}`. If a string, the query `?{query_var}={term_slug}` will be valid. + * + * @var string|bool + */ + public $query_var; + + /** + * Works much like a hook, in that it will be called when the count is updated. + * + * Default `_update_post_term_count()` for taxonomies attached to post types, which confirms that the objects are published before counting them. + * + * Default `_update_generic_term_count()` for taxonomies attached to other object types, such as users. + * + * @var callable + * @phpstan-var callable(int[],\WP_Taxonomy): void + */ + public $update_count_callback; + + /** + * Default term to be used for the taxonomy. + * + * @var string|array + * @phpstan-var string|array{ + * name: string, + * slug?: string, + * description?: string, + * } + */ + public $default_term; + + /** + * Whether terms in this taxonomy should be sorted in the order they are provided to `wp_set_object_terms()`. + * + * Default false. + */ + public bool $sort; + + /** + * Array of arguments to automatically use inside `wp_get_object_terms()` for this taxonomy. + * + * @var array + */ + public array $args; + + /** + * FOR INTERNAL USE ONLY! True if this taxonomy is a native or "built-in" post_type. + * + * Default false. + */ + protected bool $_builtin; +} diff --git a/vendor/johnbillion/args/src/register_term_meta.php b/vendor/johnbillion/args/src/register_term_meta.php new file mode 100644 index 0000000..3ba9760 --- /dev/null +++ b/vendor/johnbillion/args/src/register_term_meta.php @@ -0,0 +1,13 @@ +` element should have the HTML5 'required' attribute. + * + * Default false. + */ + public bool $required; + + /** + * Walker object to use to build the output. + * + * Default empty which results in a `Walker_CategoryDropdown` instance being used. + * + * @var \Walker + */ + public $walker; +} diff --git a/vendor/johnbillion/args/src/wp_dropdown_languages.php b/vendor/johnbillion/args/src/wp_dropdown_languages.php new file mode 100644 index 0000000..738da5a --- /dev/null +++ b/vendor/johnbillion/args/src/wp_dropdown_languages.php @@ -0,0 +1,86 @@ + + */ + public array $post_category; + + /** + * Array of tag names, slugs, or IDs. + * + * Default empty. + * + * @var array + */ + public array $tags_input; + + /** + * Array of taxonomy terms keyed by their taxonomy name. + * + * Default empty. + * + * @var array + */ + public array $tax_input; + + /** + * Array of post meta values keyed by their post meta key. + * + * Default empty. + * + * @var array + */ + public array $meta_input; + + /** + * The post ID to be used when inserting a new post. If specified, must not match any existing post ID. + * + * Default 0. + */ + public int $import_id; +} diff --git a/vendor/johnbillion/args/src/wp_insert_term.php b/vendor/johnbillion/args/src/wp_insert_term.php new file mode 100644 index 0000000..2018e5d --- /dev/null +++ b/vendor/johnbillion/args/src/wp_insert_term.php @@ -0,0 +1,40 @@ + + */ + public array $meta_input; +} diff --git a/vendor/johnbillion/args/src/wp_nav_menu.php b/vendor/johnbillion/args/src/wp_nav_menu.php new file mode 100644 index 0000000..d9b3994 --- /dev/null +++ b/vendor/johnbillion/args/src/wp_nav_menu.php @@ -0,0 +1,148 @@ + true, + + # Show all posts on the post type archive: + 'archive' => [ + 'nopaging' => true, + ], + + # Add some custom columns to the admin screen: + 'admin_cols' => [ + 'story_featured_image' => [ + 'title' => 'Illustration', + 'featured_image' => 'thumbnail' + ], + 'story_published' => [ + 'title_icon' => 'dashicons-calendar-alt', + 'meta_key' => 'published_date', + 'date_format' => 'd/m/Y' + ], + 'story_genre' => [ + 'taxonomy' => 'genre' + ], + ], + + # Add some dropdown filters to the admin screen: + 'admin_filters' => [ + 'story_genre' => [ + 'taxonomy' => 'genre' + ], + 'story_rating' => [ + 'meta_key' => 'star_rating', + ], + ], + + ], [ + + # Override the base names used for labels: + 'singular' => 'Story', + 'plural' => 'Stories', + 'slug' => 'stories', + + ] ); + + register_extended_taxonomy( 'genre', 'story', [ + + # Use radio buttons in the meta box for this taxonomy on the post editing screen: + 'meta_box' => 'radio', + + # Add a custom column to the admin screen: + 'admin_cols' => [ + 'updated' => [ + 'title_cb' => function() { + return 'Last Updated'; + }, + 'meta_key' => 'updated_date', + 'date_format' => 'd/m/Y' + ], + ], + + ] ); +} ); +``` + +Bam, we now have: + +* A 'Stories' post type, with correctly generated labels and post updated messages, three custom columns in the admin area (two of which are sortable), stories added to the main RSS feed, and all stories displayed on the post type archive. +* A 'Genre' taxonomy attached to the 'Stories' post type, with correctly generated labels and term updated messages, and a custom column in the admin area. + +The `register_extended_post_type()` and `register_extended_taxonomy()` functions are ultimately wrappers for the `register_post_type()` and `register_taxonomy()` functions in WordPress core, so any of the parameters from those functions can be used. + +There's quite a bit more you can do. [See the wiki for full documentation.](https://github.com/johnbillion/extended-cpts/wiki) + +## Contributing and Testing ## + +Please see [CONTRIBUTING.md](CONTRIBUTING.md) for information on contributing. + +## License: GPLv2 or later ## + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. diff --git a/vendor/johnbillion/extended-cpts/composer.json b/vendor/johnbillion/extended-cpts/composer.json new file mode 100644 index 0000000..6959d2b --- /dev/null +++ b/vendor/johnbillion/extended-cpts/composer.json @@ -0,0 +1,80 @@ +{ + "name": "johnbillion/extended-cpts", + "description": "A library which provides extended functionality to WordPress custom post types and taxonomies.", + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "John Blackbourn", + "homepage": "https://johnblackbourn.com/" + } + ], + "homepage": "https://github.com/johnbillion/extended-cpts/", + "require": { + "php": ">= 7.4.0", + "johnbillion/args": "^1.4.1 || ^2.0" + }, + "require-dev": { + "automattic/phpcs-neutron-standard": "1.7.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "johnbillion/falsey-assertequals-detector": "*", + "johnbillion/plugin-infrastructure": "dev-trunk", + "johnbillion/wp-compat": "0.3.1", + "lucatume/wp-browser": "3.2.3", + "phpcompatibility/phpcompatibility-wp": "2.1.5", + "phpstan/phpstan": "1.12.12", + "phpstan/phpstan-phpunit": "1.4.1", + "roots/wordpress-core-installer": "1.100.0", + "roots/wordpress-full": "*", + "szepeviktor/phpstan-wordpress": "1.3.5", + "wp-coding-standards/wpcs": "2.3.0" + }, + "autoload": { + "psr-4": { + "ExtCPTs\\": "src", + "ExtCPTs\\Tests\\": "tests/integration" + }, + "files": [ + "functions.php" + ] + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "roots/wordpress-core-installer": true + }, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "wordpress-install-dir": "vendor/wordpress/wordpress" + }, + "scripts": { + "test": [ + "@composer validate --strict --no-check-lock", + "@test:phpstan", + "@test:phpcs", + "@test:start", + "@test:integration", + "@test:stop" + ], + "test:destroy": [ + "tests-destroy" + ], + "test:integration": [ + "integration-tests" + ], + "test:phpcs": [ + "phpcs -nps --colors --report-code --report-summary --report-width=80 --cache=tests/cache/phpcs.json --basepath='./' ." + ], + "test:phpstan": [ + "codecept build", + "phpstan analyze -v --memory-limit=1024M" + ], + "test:start": [ + "tests-start" + ], + "test:stop": [ + "tests-stop" + ] + } +} diff --git a/vendor/johnbillion/extended-cpts/extended-cpts.php b/vendor/johnbillion/extended-cpts/extended-cpts.php new file mode 100755 index 0000000..a7304fb --- /dev/null +++ b/vendor/johnbillion/extended-cpts/extended-cpts.php @@ -0,0 +1,32 @@ + + * @link https://github.com/johnbillion/extended-cpts + * @copyright 2012-2024 John Blackbourn + * @license GPL v2 or later + * @version 5.0.11 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +require_once __DIR__ . '/functions.php'; +require_once __DIR__ . '/src/PostType.php'; +require_once __DIR__ . '/src/PostTypeAdmin.php'; +require_once __DIR__ . '/src/Taxonomy.php'; +require_once __DIR__ . '/src/TaxonomyAdmin.php'; +require_once __DIR__ . '/src/Walker/Checkboxes.php'; +require_once __DIR__ . '/src/Walker/Dropdown.php'; +require_once __DIR__ . '/src/Walker/Radios.php'; diff --git a/vendor/johnbillion/extended-cpts/functions.php b/vendor/johnbillion/extended-cpts/functions.php new file mode 100644 index 0000000..17847a5 --- /dev/null +++ b/vendor/johnbillion/extended-cpts/functions.php @@ -0,0 +1,136 @@ +init(); + + if ( is_admin() ) { + $admin = new PostTypeAdmin( $cpt, $cpt->args ); + $admin->init(); + } + + return $cpt; +} + +/** + * Registers a custom taxonomy. + * + * The `$args` parameter accepts all the standard arguments for `register_taxonomy()` in addition to several custom + * arguments that provide extended functionality. Some of the default arguments differ from the defaults in + * `register_taxonomy()`. + * + * @link https://github.com/johnbillion/extended-cpts/wiki/Registering-taxonomies + * @see register_taxonomy() for default arguments. + * + * @param string $taxonomy The taxonomy name. + * @param string|string[] $object_type Name(s) of the object type(s) for the taxonomy. + * @param mixed[] $args { + * Optional. The taxonomy arguments. + * + * @type string $meta_box The name of the custom meta box to use on the post editing screen for this + * taxonomy. Three custom meta boxes are provided: 'radio' for a meta box with radio + * inputs, 'simple' for a meta box with a simplified list of checkboxes, and + * 'dropdown' for a meta box with a dropdown menu. You can also pass the name of a + * callback function, eg my_super_meta_box(), or boolean false to remove the meta + * box. Default null, meaning the standard meta box is used. + * @type bool $checked_ontop Whether to always show checked terms at the top of the meta box. This allows you + * to override WordPress' default behaviour if necessary. Default false if you're + * using a custom meta box (see the $meta_box argument), default true otherwise. + * @type bool $dashboard_glance Whether to show this taxonomy on the 'At a Glance' section of the admin dashboard. + * Default false. + * @type array $admin_cols Associative array of admin screen columns to show for this taxonomy. See the + * `TaxonomyAdmin::cols()` method for more information. + * @type bool $exclusive This parameter isn't feature complete. All it does currently is set the meta box + * to the 'radio' meta box, thus meaning any given post can only have one term + * associated with it for that taxonomy. 'exclusive' isn't really the right name for + * this, as terms aren't exclusive to a post, but rather each post can exclusively + * have only one term. It's not feature complete because you can edit a post in + * Quick Edit and give it more than one term from the taxonomy. + * @type bool $allow_hierarchy All this does currently is disable hierarchy in the taxonomy's rewrite rules. + * Default false. + * } + * @param string[] $names { + * Optional. The plural, singular, and slug names. + * + * @type string $plural The plural form of the taxonomy name. + * @type string $singular The singular form of the taxonomy name. + * @type string $slug The slug used in the term permalinks for this taxonomy. + * } + * @phpstan-param array{ + * plural?: string, + * singular?: string, + * slug?: string, + * } $names + * @return Taxonomy + */ +function register_extended_taxonomy( string $taxonomy, $object_type, array $args = [], array $names = [] ): Taxonomy { + if ( ! did_action( 'init' ) ) { + trigger_error( esc_html__( 'Taxonomies must be registered on the "init" hook.', 'extended-cpts' ), E_USER_WARNING ); + } + + $taxo = new Taxonomy( $taxonomy, (array) $object_type, $args, $names ); + $taxo->init(); + + if ( is_admin() ) { + $admin = new TaxonomyAdmin( $taxo, $taxo->args ); + $admin->init(); + } + + return $taxo; +} diff --git a/vendor/johnbillion/extended-cpts/src/Args/PostType.php b/vendor/johnbillion/extended-cpts/src/Args/PostType.php new file mode 100644 index 0000000..97aa728 --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/Args/PostType.php @@ -0,0 +1,91 @@ + + */ + public array $admin_cols; + + /** + * Associative array of admin screen filters to show for this post type. + * + * @var array + */ + public array $admin_filters; + + /** + * Associative array of query vars to override on this post type's archive. + * + * @var array + */ + public array $archive; + + /** + * Force the use of the block editor for this post type. Must be used in + * combination with the `show_in_rest` argument. + * + * The primary use of this argument + * is to prevent the block editor from being used by setting it to false when + * `show_in_rest` is set to true. + */ + public bool $block_editor; + + /** + * Whether to show this post type on the 'At a Glance' section of the admin + * dashboard. + * + * Default true. + */ + public bool $dashboard_glance; + + /** + * Whether to show this post type on the 'Recently Published' section of the + * admin dashboard. + * + * Default true. + */ + public bool $dashboard_activity; + + /** + * Placeholder text which appears in the title field for this post type. + */ + public string $enter_title_here; + + /** + * Text which replaces the 'Featured Image' phrase for this post type. + */ + public string $featured_image; + + /** + * Whether to show Quick Edit links for this post type. + * + * Default true. + */ + public bool $quick_edit; + + /** + * Whether to include this post type in the site's main feed. + * + * Default false. + */ + public bool $show_in_feed; + + /** + * Associative array of query vars and their parameters for front end filtering. + * + * @var array + */ + public array $site_filters; + + /** + * Associative array of query vars and their parameters for front end sorting. + * + * @var array + */ + public array $site_sortables; +} diff --git a/vendor/johnbillion/extended-cpts/src/Args/Taxonomy.php b/vendor/johnbillion/extended-cpts/src/Args/Taxonomy.php new file mode 100644 index 0000000..f29ef62 --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/Args/Taxonomy.php @@ -0,0 +1,67 @@ + + */ + public array $admin_cols; + + /** + * This parameter isn't feature complete. All it does currently is set the meta box + * to the 'radio' meta box, thus meaning any given post can only have one term + * associated with it for that taxonomy. + * + * 'exclusive' isn't really the right name for this, as terms aren't exclusive to a + * post, but rather each post can exclusively have only one term. It's not feature + * complete because you can edit a post in Quick Edit and give it more than one term + * from the taxonomy. + */ + public bool $exclusive; + + /** + * All this does currently is disable hierarchy in the taxonomy's rewrite rules. + * + * Default false. + */ + public bool $allow_hierarchy; +} diff --git a/vendor/johnbillion/extended-cpts/src/ExtendedRewriteTesting.php b/vendor/johnbillion/extended-cpts/src/ExtendedRewriteTesting.php new file mode 100644 index 0000000..b3e5ecd --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/ExtendedRewriteTesting.php @@ -0,0 +1,74 @@ +> + */ + abstract public function get_tests(): array; + + /** + * @param array $struct + * @param array $additional + * @return array + */ + public function get_rewrites( array $struct, array $additional ): array { + global $wp_rewrite; + + if ( ! $wp_rewrite->using_permalinks() ) { + return []; + } + + $new = []; + $rules = $wp_rewrite->generate_rewrite_rules( + $struct['struct'], + $struct['ep_mask'], + $struct['paged'], + $struct['feed'], + $struct['forcomments'], + $struct['walk_dirs'], + $struct['endpoints'] + ); + $rules = array_merge( $rules, $additional ); + $feedregex = implode( '|', $wp_rewrite->feeds ); + $replace = [ + '(.+?)' => 'hello', + '.+?' => 'hello', + '([^/]+)' => 'world', + '[^/]+' => 'world', + '(?:/([0-9]+))?' => '/456', + '([0-9]{4})' => date( 'Y' ), + '[0-9]{4}' => date( 'Y' ), + '([0-9]{1,2})' => date( 'm' ), + '[0-9]{1,2}' => date( 'm' ), + '([0-9]{1,})' => '123', + '[0-9]{1,}' => '789', + '([0-9]+)' => date( 'd' ), + '[0-9]+' => date( 'd' ), + "({$feedregex})" => end( $wp_rewrite->feeds ), + '/?' => '/', + '$' => '', + ]; + + foreach ( $rules as $regex => $result ) { + $regex = str_replace( array_keys( $replace ), $replace, $regex ); + // Change '$2' to '$matches[2]' + $result = preg_replace( '/\$([0-9]+)/', '\$matches[$1]', $result ); + $new[ "/{$regex}" ] = $result; + if ( false !== strpos( $regex, $replace['(?:/([0-9]+))?'] ) ) { + // Add an extra rule for this optional block + $regex = str_replace( $replace['(?:/([0-9]+))?'], '', $regex ); + $new[ "/{$regex}" ] = $result; + } + } + + return $new; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/PostType.php b/vendor/johnbillion/extended-cpts/src/PostType.php new file mode 100644 index 0000000..a6f66f1 --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/PostType.php @@ -0,0 +1,837 @@ + + */ + protected array $defaults = [ + 'public' => true, + 'menu_position' => 6, + 'capability_type' => 'page', + 'hierarchical' => true, + 'supports' => [ + 'title', + 'editor', + 'thumbnail', + ], + 'site_filters' => null, # Custom arg + 'site_sortables' => null, # Custom arg + 'show_in_feed' => false, # Custom arg + 'archive' => null, # Custom arg + 'featured_image' => null, # Custom arg + ]; + + public string $post_type; + + public string $post_slug; + + public string $post_singular; + + public string $post_plural; + + public string $post_singular_low; + + public string $post_plural_low; + + /** + * @var array + */ + public array $args; + + /** + * Class constructor. + * + * @see register_extended_post_type() + * + * @param string $post_type The post type name. + * @param array $args Optional. The post type arguments. + * @param array $names Optional. The plural, singular, and slug names. + * @phpstan-param array{ + * plural?: string, + * singular?: string, + * slug?: string, + * } $names + */ + public function __construct( string $post_type, array $args = [], array $names = [] ) { + /** + * Filter the arguments for a post type. + * + * @since 4.4.1 + * + * @param array $args The post type arguments. + * @param string $post_type The post type name. + */ + $args = apply_filters( 'ext-cpts/args', $args, $post_type ); + + /** + * Filter the arguments for this post type. + * + * @since 2.4.0 + * + * @param array $args The post type arguments. + */ + $args = apply_filters( "ext-cpts/{$post_type}/args", $args ); + + /** + * Filter the plural, singular, and slug names for a post type. + * + * @since 4.4.1 + * + * @param array $names The plural, singular, and slug names (if any were specified). + * @param string $post_type The post type name. + */ + $names = apply_filters( 'ext-cpts/names', $names, $post_type ); + + /** + * Filter the plural, singular, and slug names for this post type. + * + * @since 2.4.0 + * + * @param array $names The plural, singular, and slug names (if any were specified). + */ + $names = apply_filters( "ext-cpts/{$post_type}/names", $names ); + + if ( isset( $names['singular'] ) ) { + $this->post_singular = $names['singular']; + } else { + $this->post_singular = ucwords( + str_replace( + [ + '-', + '_', + ], + ' ', + $post_type + ) + ); + } + + if ( isset( $names['slug'] ) ) { + $this->post_slug = $names['slug']; + } elseif ( isset( $names['plural'] ) ) { + $this->post_slug = $names['plural']; + } else { + $this->post_slug = $post_type . 's'; + } + + if ( isset( $names['plural'] ) ) { + $this->post_plural = $names['plural']; + } else { + $this->post_plural = $this->post_singular . 's'; + } + + $this->post_type = strtolower( $post_type ); + $this->post_slug = strtolower( $this->post_slug ); + + # Build our base post type names: + # Lower-casing is not forced if the name looks like an initialism, eg. FAQ. + if ( ! preg_match( '/[A-Z]{2,}/', $this->post_singular ) ) { + $this->post_singular_low = strtolower( $this->post_singular ); + } else { + $this->post_singular_low = $this->post_singular; + } + + if ( ! preg_match( '/[A-Z]{2,}/', $this->post_plural ) ) { + $this->post_plural_low = strtolower( $this->post_plural ); + } else { + $this->post_plural_low = $this->post_plural; + } + + # Build our labels: + # Why aren't these translatable? + # Answer: https://github.com/johnbillion/extended-cpts/pull/5#issuecomment-33756474 + $this->defaults['labels'] = [ + 'name' => $this->post_plural, + 'singular_name' => $this->post_singular, + 'menu_name' => $this->post_plural, + 'name_admin_bar' => $this->post_singular, + 'add_new' => sprintf( 'Add New %s', $this->post_singular ), + 'add_new_item' => sprintf( 'Add New %s', $this->post_singular ), + 'edit_item' => sprintf( 'Edit %s', $this->post_singular ), + 'new_item' => sprintf( 'New %s', $this->post_singular ), + 'view_item' => sprintf( 'View %s', $this->post_singular ), + 'view_items' => sprintf( 'View %s', $this->post_plural ), + 'search_items' => sprintf( 'Search %s', $this->post_plural ), + 'not_found' => sprintf( 'No %s found.', $this->post_plural_low ), + 'not_found_in_trash' => sprintf( 'No %s found in trash.', $this->post_plural_low ), + 'parent_item_colon' => sprintf( 'Parent %s:', $this->post_singular ), + 'all_items' => sprintf( 'All %s', $this->post_plural ), + 'archives' => sprintf( '%s Archives', $this->post_singular ), + 'attributes' => sprintf( '%s Attributes', $this->post_singular ), + 'insert_into_item' => sprintf( 'Insert into %s', $this->post_singular_low ), + 'uploaded_to_this_item' => sprintf( 'Uploaded to this %s', $this->post_singular_low ), + 'filter_items_list' => sprintf( 'Filter %s list', $this->post_plural_low ), + 'filter_by_date' => 'Filter by date', + 'items_list_navigation' => sprintf( '%s list navigation', $this->post_plural ), + 'items_list' => sprintf( '%s list', $this->post_plural ), + 'item_published' => sprintf( '%s published.', $this->post_singular ), + 'item_published_privately' => sprintf( '%s published privately.', $this->post_singular ), + 'item_reverted_to_draft' => sprintf( '%s reverted to draft.', $this->post_singular ), + 'item_scheduled' => sprintf( '%s scheduled.', $this->post_singular ), + 'item_updated' => sprintf( '%s updated.', $this->post_singular ), + 'item_link' => sprintf( '%s Link', $this->post_singular ), + 'item_link_description' => sprintf( 'A link to a %s.', $this->post_singular_low ), + 'item_trashed' => sprintf( '%s trashed.', $this->post_singular ), + 'template_name' => sprintf( 'Single item: %s', $this->post_singular ), + ]; + + # Build the featured image labels: + if ( isset( $args['featured_image'] ) ) { + $featured_image_low = strtolower( $args['featured_image'] ); + $this->defaults['labels']['featured_image'] = $args['featured_image']; + $this->defaults['labels']['set_featured_image'] = sprintf( 'Set %s', $featured_image_low ); + $this->defaults['labels']['remove_featured_image'] = sprintf( 'Remove %s', $featured_image_low ); + $this->defaults['labels']['use_featured_image'] = sprintf( 'Use as %s', $featured_image_low ); + } + + # Only set default rewrites if we need them + if ( isset( $args['public'] ) && ! $args['public'] ) { + $this->defaults['rewrite'] = false; + } else { + $this->defaults['rewrite'] = [ + 'slug' => $this->post_slug, + 'with_front' => false, + ]; + } + + # Merge our args with the defaults: + $this->args = array_merge( $this->defaults, $args ); + + # This allows the 'labels' and 'rewrite' args to contain all, some, or no values: + foreach ( [ 'labels', 'rewrite' ] as $arg ) { + if ( isset( $args[ $arg ] ) && is_array( $args[ $arg ] ) && is_array( $this->defaults[ $arg ] ) ) { + $this->args[ $arg ] = array_merge( $this->defaults[ $arg ], $args[ $arg ] ); + } + } + + # Enable post type archives by default + if ( ! isset( $this->args['has_archive'] ) ) { + $this->args['has_archive'] = $this->args['public']; + } + } + + /** + * Initialise the post type by adding the necessary actions and filters. + */ + public function init(): void { + # Front-end sortables: + if ( $this->args['site_sortables'] && ! is_admin() ) { + add_action( 'pre_get_posts', [ $this, 'maybe_sort_by_fields' ] ); + add_filter( 'posts_clauses', [ $this, 'maybe_sort_by_taxonomy' ], 10, 2 ); + } + + # Front-end filters: + if ( $this->args['site_filters'] && ! is_admin() ) { + add_action( 'pre_get_posts', [ $this, 'maybe_filter' ] ); + add_filter( 'query_vars', [ $this, 'add_query_vars' ] ); + } + + # Post type in the site's main feed: + if ( $this->args['show_in_feed'] ) { + add_filter( 'request', [ $this, 'add_to_feed' ] ); + } + + # Post type archive query vars: + if ( $this->args['archive'] && ! is_admin() ) { + add_filter( 'parse_request', [ $this, 'override_private_query_vars' ], 1 ); + } + + # Custom post type permastruct: + if ( $this->args['rewrite'] && ! empty( $this->args['rewrite']['permastruct'] ) ) { + add_action( 'registered_post_type', [ $this, 'registered_post_type' ], 1, 2 ); + add_filter( 'post_type_link', [ $this, 'post_type_link' ], 1, 2 ); + } + + # Rewrite testing: + if ( $this->args['rewrite'] ) { + add_filter( 'rewrite_testing_tests', [ $this, 'rewrite_testing_tests' ], 1 ); + } + + # Register post type: + $this->register_post_type(); + + /** + * Fired when the extended post type instance is set up. + * + * @since 3.1.0 + * + * @param \ExtCPTs\PostType $instance The extended post type instance. + */ + do_action( "ext-cpts/{$this->post_type}/instance", $this ); + } + + /** + * Set the relevant query vars for filtering posts by our front-end filters. + * + * @param WP_Query $wp_query The current WP_Query object. + */ + public function maybe_filter( WP_Query $wp_query ): void { + if ( empty( $wp_query->query['post_type'] ) || ! in_array( $this->post_type, (array) $wp_query->query['post_type'], true ) ) { + return; + } + + $vars = self::get_filter_vars( $wp_query->query, $this->args['site_filters'], $this->post_type ); + + if ( empty( $vars ) ) { + return; + } + + foreach ( $vars as $key => $value ) { + if ( is_array( $value ) ) { + $query = $wp_query->get( $key ); + if ( empty( $query ) ) { + $query = []; + } + $value = array_merge( $query, $value ); + } + $wp_query->set( $key, $value ); + } + } + + /** + * Set the relevant query vars for sorting posts by our front-end sortables. + * + * @param WP_Query $wp_query The current WP_Query object. + */ + public function maybe_sort_by_fields( WP_Query $wp_query ): void { + if ( empty( $wp_query->query['post_type'] ) || ! in_array( $this->post_type, (array) $wp_query->query['post_type'], true ) ) { + return; + } + + // If we've not specified an order: + if ( empty( $wp_query->query['orderby'] ) ) { + // Loop over our sortables to find the default sort field (if there is one): + foreach ( $this->args['site_sortables'] as $id => $col ) { + if ( is_array( $col ) && isset( $col['default'] ) ) { + // @TODO Don't set 'order' if 'orderby' is an array (WP 4.0+) + $wp_query->query['orderby'] = $id; + $wp_query->query['order'] = ( 'desc' === strtolower( $col['default'] ) ? 'desc' : 'asc' ); + break; + } + } + } + + $sort = self::get_sort_field_vars( $wp_query->query, $this->args['site_sortables'] ); + + if ( empty( $sort ) ) { + return; + } + + foreach ( $sort as $key => $value ) { + $wp_query->set( $key, $value ); + } + } + + /** + * Filter the query's SQL clauses so we can sort posts by taxonomy terms. + * + * @param array $clauses Array of the current query's SQL clauses. + * @param WP_Query $wp_query The current `WP_Query` object. + * @return array Array of SQL clauses. + */ + public function maybe_sort_by_taxonomy( array $clauses, WP_Query $wp_query ): array { + if ( empty( $wp_query->query['post_type'] ) || ! in_array( $this->post_type, (array) $wp_query->query['post_type'], true ) ) { + return $clauses; + } + + $sort = self::get_sort_taxonomy_clauses( $clauses, $wp_query->query, $this->args['site_sortables'] ); + + if ( empty( $sort ) ) { + return $clauses; + } + + return array_merge( $clauses, $sort ); + } + + /** + * Get the array of private query vars for the given filters, to apply to the current query in order to filter it by the + * given public query vars. + * + * @param array $query The public query vars, usually from `$wp_query->query`. + * @param array $filters The filters valid for this query (usually the value of the `admin_filters` or + * `site_filters` argument when registering an extended post type). + * @param string $post_type The post type name. + * @return array The list of private query vars to apply to the query. + */ + public static function get_filter_vars( array $query, array $filters, string $post_type ): array { + $return = []; + + foreach ( $filters as $filter_key => $filter ) { + $meta_query = []; + $date_query = []; + + if ( ! isset( $query[ $filter_key ] ) || ( '' === $query[ $filter_key ] ) ) { + continue; + } + + if ( isset( $filter['cap'] ) && ! current_user_can( $filter['cap'] ) ) { + continue; + } + + $hook = "ext-cpts/{$post_type}/filter-query/{$filter_key}"; + + if ( has_filter( $hook ) ) { + /** + * Allows a filter's private query vars to be overridden. + * + * @since 4.3.0 + * + * @param array $return The private query vars. + * @param array $query The public query vars. + * @param array $filter The filter arguments. + */ + $return = apply_filters( $hook, $return, $query, $filter ); + continue; + } + + if ( isset( $filter['meta_key'] ) ) { + $meta_query = [ + 'key' => $filter['meta_key'], + 'value' => wp_unslash( $query[ $filter_key ] ), + ]; + } elseif ( isset( $filter['meta_search_key'] ) ) { + $meta_query = [ + 'key' => $filter['meta_search_key'], + 'value' => wp_unslash( $query[ $filter_key ] ), + 'compare' => 'LIKE', + ]; + } elseif ( isset( $filter['meta_key_exists'] ) ) { + $meta_query = [ + 'key' => wp_unslash( $query[ $filter_key ] ), + 'compare' => 'EXISTS', + ]; + } elseif ( isset( $filter['meta_exists'] ) ) { + $meta_query = [ + 'key' => wp_unslash( $query[ $filter_key ] ), + 'compare' => 'NOT IN', + 'value' => [ '', '0', 'false', 'null' ], + ]; + } elseif ( isset( $filter['post_date'] ) ) { + $date_query = [ + $filter['post_date'] => wp_unslash( $query[ $filter_key ] ), + 'inclusive' => true, + ]; + } else { + continue; + } + + if ( isset( $filter['meta_query'] ) ) { + $meta_query = array_merge( $meta_query, $filter['meta_query'] ); + } + + if ( isset( $filter['date_query'] ) ) { + $date_query = array_merge( $date_query, $filter['date_query'] ); + } + + if ( ! empty( $meta_query ) ) { + $return['meta_query'][] = $meta_query; + } + + if ( ! empty( $date_query ) ) { + $return['date_query'][] = $date_query; + } + } + + return $return; + } + + /** + * Get the array of private and public query vars for the given sortables, to apply to the current query in order to + * sort it by the requested orderby field. + * + * @param array $vars The public query vars, usually from `$wp_query->query`. + * @param array $sortables The sortables valid for this query (usually the value of the `admin_cols` or + * `site_sortables` argument when registering an extended post type. + * @return array The list of private and public query vars to apply to the query. + */ + public static function get_sort_field_vars( array $vars, array $sortables ): array { + if ( ! isset( $vars['orderby'] ) ) { + return []; + } + + if ( ! is_string( $vars['orderby'] ) ) { + return []; + } + + if ( ! isset( $sortables[ $vars['orderby'] ] ) ) { + return []; + } + + $orderby = $sortables[ $vars['orderby'] ]; + + if ( ! is_array( $orderby ) ) { + return []; + } + + if ( isset( $orderby['sortable'] ) && ! $orderby['sortable'] ) { + return []; + } + + $return = []; + + if ( isset( $orderby['meta_key'] ) ) { + $return['meta_key'] = $orderby['meta_key']; + $return['orderby'] = 'meta_value'; + // @TODO meta_value_num + } elseif ( isset( $orderby['post_field'] ) ) { + $field = str_replace( 'post_', '', $orderby['post_field'] ); + $return['orderby'] = $field; + } + + if ( isset( $vars['order'] ) ) { + $return['order'] = $vars['order']; + } + + return $return; + } + + /** + * Get the array of SQL clauses for the given sortables, to apply to the current query in order to + * sort it by the requested orderby field. + * + * @param array $clauses The query's SQL clauses. + * @param array $vars The public query vars, usually from `$wp_query->query`. + * @param array $sortables The sortables valid for this query (usually the value of the `admin_cols` or + * `site_sortables` argument when registering an extended post type). + * @return array The list of SQL clauses to apply to the query. + */ + public static function get_sort_taxonomy_clauses( array $clauses, array $vars, array $sortables ): array { + global $wpdb; + + if ( ! isset( $vars['orderby'] ) ) { + return []; + } + + if ( ! is_string( $vars['orderby'] ) ) { + return []; + } + + if ( ! isset( $sortables[ $vars['orderby'] ] ) ) { + return []; + } + + $orderby = $sortables[ $vars['orderby'] ]; + + if ( ! is_array( $orderby ) ) { + return []; + } + + if ( isset( $orderby['sortable'] ) && ! $orderby['sortable'] ) { + return []; + } + + if ( ! isset( $orderby['taxonomy'] ) ) { + return []; + } + + # Taxonomy term ordering courtesy of http://scribu.net/wordpress/sortable-taxonomy-columns.html + $clauses['join'] .= " + LEFT OUTER JOIN {$wpdb->term_relationships} as ext_cpts_tr + ON ( {$wpdb->posts}.ID = ext_cpts_tr.object_id ) + LEFT OUTER JOIN {$wpdb->term_taxonomy} as ext_cpts_tt + ON ( ext_cpts_tr.term_taxonomy_id = ext_cpts_tt.term_taxonomy_id ) + LEFT OUTER JOIN {$wpdb->terms} as ext_cpts_t + ON ( ext_cpts_tt.term_id = ext_cpts_t.term_id ) + "; + $clauses['where'] .= $wpdb->prepare( ' AND ( taxonomy = %s OR taxonomy IS NULL )', $orderby['taxonomy'] ); + $clauses['groupby'] = 'ext_cpts_tr.object_id'; + $clauses['orderby'] = 'GROUP_CONCAT( ext_cpts_t.name ORDER BY name ASC ) '; + $clauses['orderby'] .= ( isset( $vars['order'] ) && ( 'ASC' === strtoupper( $vars['order'] ) ) ) ? 'ASC' : 'DESC'; + + return $clauses; + } + + /** + * Add our filter names to the public query vars. + * + * @param array $vars Public query variables. + * @return array Updated public query variables. + */ + public function add_query_vars( array $vars ): array { + /** @var array */ + $site_filters = $this->args['site_filters']; + $filters = array_keys( $site_filters ); + + return array_merge( $vars, $filters ); + } + + /** + * Add our post type to the feed. + * + * @param array $vars Request parameters. + * @return array Updated request parameters. + */ + public function add_to_feed( array $vars ): array { + # If it's not a feed, we're not interested: + if ( ! isset( $vars['feed'] ) ) { + return $vars; + } + + if ( ! isset( $vars['post_type'] ) ) { + $vars['post_type'] = [ + 'post', + $this->post_type, + ]; + } elseif ( is_array( $vars['post_type'] ) && ( count( $vars['post_type'] ) > 1 ) ) { + $vars['post_type'][] = $this->post_type; + } + + return $vars; + } + + /** + * Add to or override our post type archive's private query vars. + * + * @param WP $wp The WP request object. + * @return WP Updated WP request object. + */ + public function override_private_query_vars( WP $wp ): WP { + # If it's not our post type, bail out: + if ( ! isset( $wp->query_vars['post_type'] ) || ( $this->post_type !== $wp->query_vars['post_type'] ) ) { + return $wp; + } + + # If it's a single post, bail out: + if ( isset( $wp->query_vars['name'] ) ) { + return $wp; + } + + # Set the vars: + foreach ( $this->args['archive'] as $var => $value ) { + $wp->query_vars[ $var ] = $value; + } + + return $wp; + } + + /** + * Action fired after a PostType is registered in order to set up the custom permalink structure for the post type. + * + * @param string $post_type Post type name. + * @param WP_Post_Type $post_type_object Post type object. + */ + public function registered_post_type( string $post_type, WP_Post_Type $post_type_object ): void { + if ( $post_type !== $this->post_type ) { + return; + } + if ( ! $post_type_object->rewrite ) { + return; + } + if ( ! is_string( $post_type_object->rewrite['permastruct'] ) ) { + return; + } + + $struct = str_replace( "%{$this->post_type}_slug%", $this->post_slug, $post_type_object->rewrite['permastruct'] ); + $struct = str_replace( '%postname%', "%{$this->post_type}%", $struct ); + + add_permastruct( $this->post_type, $struct, $post_type_object->rewrite ); + } + + /** + * Filter the post type permalink in order to populate its rewrite tags. + * + * @param string $post_link The post's permalink. + * @param WP_Post $post The post in question. + * @return string The post's permalink. + */ + public function post_type_link( string $post_link, WP_Post $post ): string { + # If it's not our post type, bail out: + if ( $this->post_type !== $post->post_type ) { + return $post_link; + } + + /** @var string */ + $date = mysql2date( 'Y m d H i s', $post->post_date ); + $date = explode( ' ', $date ); + $replacements = [ + '%year%' => $date[0], + '%monthnum%' => $date[1], + '%day%' => $date[2], + '%hour%' => $date[3], + '%minute%' => $date[4], + '%second%' => $date[5], + '%post_id%' => $post->ID, + ]; + + if ( false !== strpos( $post_link, '%author%' ) ) { + $author = get_userdata( (int) $post->post_author ); + if ( $author ) { + $replacements['%author%'] = $author->user_nicename; + } else { + $replacements['%author%'] = '-'; + } + } + + /** @var string $tax */ + foreach ( get_object_taxonomies( $post ) as $tax ) { + if ( false === strpos( $post_link, "%{$tax}%" ) ) { + continue; + } + + $terms = get_the_terms( $post, $tax ); + + if ( $terms && ! is_wp_error( $terms ) ) { + /** + * Filter the term that gets used in the `$tax` permalink token. + * + * @param WP_Term $term The `$tax` term to use in the permalink. + * @param WP_Term[] $terms Array of all `$tax` terms associated with the post. + * @param WP_Post $post The post in question. + */ + $term_object = apply_filters( "post_link_{$tax}", reset( $terms ), $terms, $post ); + + $term = $term_object->slug; + + } else { + $term = $post->post_type; + + /** + * Filter the default term that gets used in the `$tax` permalink token. + * + * @param int $term The ID of the term to use in the permalink. + * @param WP_Post $post The post in question. + */ + $default_term_id = (int) apply_filters( "default_{$tax}", get_option( "default_{$tax}", 0 ), $post ); + + if ( $default_term_id ) { + $default_term = get_term( $default_term_id, $tax ); + if ( $default_term instanceof WP_Term ) { + $term = $default_term->slug; + } + } + } + + $replacements[ "%{$tax}%" ] = $term; + } + + $post_link = str_replace( array_keys( $replacements ), $replacements, $post_link ); + + return $post_link; + } + + /** + * Add our rewrite tests to the Rewrite Rule Testing tests array. + * + * @codeCoverageIgnore + * + * @param array> $tests The existing rewrite rule tests. + * @return array> Updated rewrite rule tests. + */ + public function rewrite_testing_tests( array $tests ): array { + require_once __DIR__ . '/ExtendedRewriteTesting.php'; + require_once __DIR__ . '/PostTypeRewriteTesting.php'; + + $extended = new PostTypeRewriteTesting( $this ); + + return array_merge( $tests, $extended->get_tests() ); + } + + /** + * Registers our post type. + * + * The only difference between this and regular `register_post_type()` calls is this will trigger an error of + * `E_USER_ERROR` level if a `WP_Error` is returned. + * + */ + public function register_post_type(): void { + if ( ! isset( $this->args['query_var'] ) || ( true === $this->args['query_var'] ) ) { + $query_var = $this->post_type; + } else { + $query_var = $this->args['query_var']; + } + + $existing = get_post_type_object( $this->post_type ); + $taxonomies = get_taxonomies( + [ + 'query_var' => $query_var, + ], + 'objects' + ); + + if ( $query_var && count( $taxonomies ) ) { + // https://core.trac.wordpress.org/ticket/35089 + foreach ( $taxonomies as $tax ) { + if ( $tax->query_var === $query_var ) { + trigger_error( + esc_html( + sprintf( + /* translators: %s: Post type query variable name */ + __( 'Post type query var "%s" clashes with a taxonomy query var of the same name', 'extended-cpts' ), + $query_var + ) + ), + E_USER_ERROR + ); + } + } + } + + if ( empty( $existing ) ) { + $cpt = register_post_type( $this->post_type, $this->args ); + + if ( is_wp_error( $cpt ) ) { + trigger_error( esc_html( $cpt->get_error_message() ), E_USER_ERROR ); + } + } else { + # This allows us to call `register_extended_post_type()` on an existing post type to add custom functionality + # to the post type. + $this->extend( $existing ); + } + } + + /** + * Extends an existing post type object. Currently only handles labels. + * + * @param WP_Post_Type $pto A post type object. + */ + public function extend( WP_Post_Type $pto ): void { + # Merge core with overridden labels + $this->args['labels'] = array_merge( (array) get_post_type_labels( $pto ), $this->args['labels'] ); + + $GLOBALS['wp_post_types'][ $pto->name ]->labels = (object) $this->args['labels']; + } + + /** + * Helper function for registering a taxonomy and adding it to this post type. + * + * Accepts the same parameters as `register_extended_taxonomy()`, minus the `$object_type` parameter. + * + * Example usage: + * + * $events = register_extended_post_type( 'event' ); + * $location = $events->add_taxonomy( 'location' ); + * + * @param string $taxonomy The taxonomy name. + * @param array $args Optional. The taxonomy arguments. + * @param array $names Optional. An associative array of the plural, singular, and slug names. + * @return WP_Taxonomy Taxonomy object. + */ + public function add_taxonomy( string $taxonomy, array $args = [], array $names = [] ): WP_Taxonomy { + if ( taxonomy_exists( $taxonomy ) ) { + register_taxonomy_for_object_type( $taxonomy, $this->post_type ); + } else { + register_extended_taxonomy( $taxonomy, $this->post_type, $args, $names ); + } + + /** @var WP_Taxonomy */ + $tax = get_taxonomy( $taxonomy ); + + return $tax; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/PostTypeAdmin.php b/vendor/johnbillion/extended-cpts/src/PostTypeAdmin.php new file mode 100644 index 0000000..f28c22e --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/PostTypeAdmin.php @@ -0,0 +1,1504 @@ + + */ + protected array $defaults = [ + 'quick_edit' => true, # Custom arg + 'dashboard_glance' => true, # Custom arg + 'dashboard_activity' => true, # Custom arg + 'admin_cols' => null, # Custom arg + 'admin_filters' => null, # Custom arg + 'enter_title_here' => null, # Custom arg + 'block_editor' => null, # Custom arg + ]; + + public PostType $cpt; + + /** + * @var array + */ + public array $args; + + /** + * @var array + */ + protected array $_cols; + + /** + * @var array + */ + protected ?array $the_cols = null; + + /** + * @var array + */ + protected array $connection_exists = []; + + /** + * Class constructor. + * + * @param PostType $cpt An extended post type object. + * @param array $args Optional. The post type arguments. + */ + public function __construct( PostType $cpt, array $args = [] ) { + $this->cpt = $cpt; + # Merge our args with the defaults: + $this->args = array_merge( $this->defaults, $args ); + } + + /** + * Initialise the admin features of the post type by adding the necessary actions and filters. + */ + public function init(): void { + # Admin columns: + if ( $this->args['admin_cols'] ) { + add_filter( 'manage_posts_columns', [ $this, '_log_default_cols' ], 0 ); + add_filter( 'manage_pages_columns', [ $this, '_log_default_cols' ], 0 ); + add_filter( 'manage_media_columns', [ $this, '_log_default_cols' ], 0 ); + if ( 'attachment' === $this->cpt->post_type ) { + add_filter( 'manage_upload_sortable_columns', [ $this, 'sortables' ] ); + add_filter( 'manage_media_columns', [ $this, 'cols' ] ); + add_action( 'manage_media_custom_column', [ $this, 'col' ], 10, 2 ); + } else { + add_filter( "manage_edit-{$this->cpt->post_type}_sortable_columns", [ $this, 'sortables' ] ); + add_filter( "manage_{$this->cpt->post_type}_posts_columns", [ $this, 'cols' ] ); + add_action( "manage_{$this->cpt->post_type}_posts_custom_column", [ $this, 'col' ], 10, 2 ); + } + add_action( 'load-edit.php', [ $this, 'default_sort' ] ); + add_action( 'pre_get_posts', [ $this, 'maybe_sort_by_fields' ] ); + add_filter( 'posts_clauses', [ $this, 'maybe_sort_by_taxonomy' ], 10, 2 ); + } + + # Admin filters: + if ( $this->args['admin_filters'] ) { + add_action( 'load-edit.php', [ $this, 'default_filter' ] ); + add_action( 'pre_get_posts', [ $this, 'maybe_filter' ] ); + add_filter( 'query_vars', [ $this, 'add_query_vars' ] ); + add_action( 'restrict_manage_posts', [ $this, 'filters' ] ); + } + + # 'Enter title here' filter: + if ( $this->args['enter_title_here'] ) { + add_filter( 'enter_title_here', [ $this, 'enter_title_here' ], 10, 2 ); + } + + # Block editor filter: + if ( ! is_null( $this->args['block_editor'] ) && is_bool( $this->args['block_editor'] ) ) { + add_filter( 'use_block_editor_for_post_type', [ $this, 'block_editor' ], 101, 2 ); + } + + # Hide month filter: + if ( isset( $this->args['admin_filters']['m'] ) && ! $this->args['admin_filters']['m'] ) { + add_filter( 'disable_months_dropdown', [ $this, 'filter_disable_months_dropdown' ], 10, 2 ); + } + + # Quick Edit: + if ( ! $this->args['quick_edit'] ) { + add_filter( 'post_row_actions', [ $this, 'remove_quick_edit_action' ], 10, 2 ); + add_filter( 'page_row_actions', [ $this, 'remove_quick_edit_action' ], 10, 2 ); + add_filter( "bulk_actions-edit-{$this->cpt->post_type}", [ $this, 'remove_quick_edit_menu' ] ); + } + + # 'At a Glance' dashboard panels: + if ( $this->args['dashboard_glance'] ) { + add_filter( 'dashboard_glance_items', [ $this, 'glance_items' ], $this->cpt->args['menu_position'] ); + } + + # 'Recently Published' dashboard section: + if ( $this->args['dashboard_activity'] ) { + add_filter( 'dashboard_recent_posts_query_args', [ $this, 'dashboard_activity' ] ); + } + + # Post updated messages: + add_filter( 'post_updated_messages', [ $this, 'post_updated_messages' ], 1 ); + add_filter( 'bulk_post_updated_messages', [ $this, 'bulk_post_updated_messages' ], 1, 2 ); + + /** + * Fired when the extended post type admin instance is set up. + * + * @since 5.0.0 + * + * @param \ExtCPTs\PostTypeAdmin $instance The extended post type admin instance. + */ + do_action( "ext-cpts/{$this->cpt->post_type}/admin-instance", $this ); + } + + /** + * Removes the default 'Months' drop-down from the post list table. + * + * @param bool $disable Whether to disable the drop-down. + * @param string $post_type The post type. + * @return bool Whether to disable the drop-down. + */ + public function filter_disable_months_dropdown( bool $disable, string $post_type ): bool { + if ( $post_type === $this->cpt->post_type ) { + return true; + } + + return $disable; + } + + /** + * Sets the default sort field and sort order on our post type admin screen. + */ + public function default_sort(): void { + if ( self::get_current_post_type() !== $this->cpt->post_type ) { + return; + } + + # If we've already ordered the screen, bail out: + if ( isset( $_GET['orderby'] ) ) { + return; + } + + # Loop over our columns to find the default sort column (if there is one): + foreach ( $this->args['admin_cols'] as $id => $col ) { + if ( is_array( $col ) && isset( $col['default'] ) ) { + $_GET['orderby'] = $id; + $_GET['order'] = ( 'desc' === strtolower( $col['default'] ) ? 'desc' : 'asc' ); + break; + } + } + } + + /** + * Sets the default sort field and sort order on our post type admin screen. + */ + public function default_filter(): void { + if ( self::get_current_post_type() !== $this->cpt->post_type ) { + return; + } + + # Loop over our filters to find the default filter (if there is one): + foreach ( $this->args['admin_filters'] as $id => $filter ) { + if ( isset( $_GET[ $id ] ) && '' !== $_GET[ $id ] ) { + continue; + } + + if ( is_array( $filter ) && isset( $filter['default'] ) ) { + $_GET[ $id ] = $filter['default']; + return; + } + } + } + + /** + * Sets the placeholder text for the title field for this post type. + * + * @param string $title The placeholder text. + * @param WP_Post $post The current post. + * @return string The updated placeholder text. + */ + public function enter_title_here( string $title, WP_Post $post ): string { + if ( $this->cpt->post_type !== $post->post_type ) { + return $title; + } + + return $this->args['enter_title_here']; + } + + /** + * Enable or disable the block editor if it matches this custom post type. + * + * @param bool $current_status The current status for the given post type. + * @param string $post_type The current post type. + * @return bool The updated status. + */ + public function block_editor( bool $current_status, string $post_type ): bool { + if ( $post_type === $this->cpt->post_type ) { + return $this->args['block_editor']; + } + + return $current_status; + } + + /** + * Returns the name of the post type for the current request. + * + * @return string The post type name. + */ + protected static function get_current_post_type(): string { + if ( function_exists( 'get_current_screen' ) && is_object( get_current_screen() ) && in_array( get_current_screen()->base, [ 'edit', 'upload' ], true ) ) { + return get_current_screen()->post_type; + } else { + return ''; + } + } + + /** + * Outputs custom filter controls on the admin screen for this post type. + * + * @link https://github.com/johnbillion/extended-cpts/wiki/Admin-filters + */ + public function filters(): void { + global $wpdb; + + if ( self::get_current_post_type() !== $this->cpt->post_type ) { + return; + } + + /** @var \WP_Post_Type */ + $pto = get_post_type_object( $this->cpt->post_type ); + + foreach ( $this->args['admin_filters'] as $filter_key => $filter ) { + if ( isset( $filter['cap'] ) && ! current_user_can( $filter['cap'] ) ) { + continue; + } + + $id = 'filter_' . $filter_key; + + $hook = "ext-cpts/{$this->cpt->post_type}/filter-output/{$filter_key}"; + + if ( has_action( $hook ) ) { + /** + * Allows a filter's output to be overridden. + * + * @since 4.3.0 + * + * @param \ExtCPTs\PostTypeAdmin $controller The post type admin controller instance. + * @param array $filter The filter arguments. + * @param string $id The filter's `id` attribute value. + */ + do_action( $hook, $this, $filter, $id ); + continue; + } + + if ( isset( $filter['taxonomy'] ) ) { + $tax = get_taxonomy( $filter['taxonomy'] ); + + if ( empty( $tax ) ) { + continue; + } + + $walker = new Walker\Dropdown( + [ + 'field' => 'slug', + ] + ); + + # If we haven't specified a title, use the all_items label from the taxonomy: + if ( ! isset( $filter['title'] ) ) { + $filter['title'] = $tax->labels->all_items; + } + + printf( + '', + esc_attr( $id ), + esc_html( $tax->labels->filter_by ?? $tax->labels->singular_name ) + ); + + # Output the dropdown: + wp_dropdown_categories( + [ + 'show_option_all' => $filter['title'], + 'hide_empty' => false, + 'hide_if_empty' => true, + 'hierarchical' => true, + 'show_count' => false, + 'orderby' => 'name', + 'selected_cats' => $tax->query_var ? get_query_var( $tax->query_var ) : [], + 'id' => $id, + 'name' => (string) $tax->query_var, + 'taxonomy' => $filter['taxonomy'], + 'walker' => $walker, + ] + ); + } elseif ( isset( $filter['meta_key'] ) ) { + # If we haven't specified a title, generate one from the meta key: + if ( ! isset( $filter['title'] ) ) { + $filter['title'] = str_replace( + [ + '-', + '_', + ], + ' ', + $filter['meta_key'] + ); + $filter['title'] = ucwords( $filter['title'] ) . 's'; + $filter['title'] = sprintf( 'All %s', $filter['title'] ); + } + + # If we haven't specified a label, generate one from the meta key: + if ( ! isset( $filter['label'] ) ) { + $filter['label'] = str_replace( + [ + '-', + '_', + ], + ' ', + $filter['meta_key'] + ); + $filter['label'] = ucwords( $filter['label'] ); + $filter['label'] = sprintf( 'Filter by %s', $filter['label'] ); + } + + if ( ! isset( $filter['options'] ) ) { + # Fetch all the values for our meta key: + $filter['options'] = $wpdb->get_col( + $wpdb->prepare( + " + SELECT DISTINCT meta_value + FROM {$wpdb->postmeta} as m + JOIN {$wpdb->posts} as p ON ( p.ID = m.post_id ) + WHERE m.meta_key = %s + AND m.meta_value != '' + AND p.post_type = %s + ORDER BY m.meta_value ASC + ", + $filter['meta_key'], + $this->cpt->post_type + ) + ); + } elseif ( is_callable( $filter['options'] ) ) { + $filter['options'] = call_user_func( $filter['options'] ); + } + + if ( empty( $filter['options'] ) ) { + continue; + } + + $selected = wp_unslash( get_query_var( $filter_key ) ); + + $use_key = false; + + foreach ( $filter['options'] as $k => $v ) { + if ( ! is_numeric( $k ) ) { + $use_key = true; + break; + } + } + + printf( + '', + esc_attr( $id ), + esc_html( $filter['label'] ) + ); + + # Output the dropdown: + ?> + + +   + labels->all_items; + } + + $selected = wp_unslash( get_query_var( $filter_key ) ); + $fields = $filter['meta_exists'] ?? $filter['meta_key_exists']; + + if ( 1 === count( $fields ) ) { + # Output a checkbox: + foreach ( $fields as $v => $t ) { + ?> + > + labels->name; + } + + printf( + '', + esc_attr( $id ), + esc_html( $filter['label'] ) + ); + + # Output a dropdown: + ?> + + +   + + %2$s', + esc_attr( $id ), + esc_html( $filter['label'] ) + ); + + if ( ! isset( $filter['options'] ) ) { + # Fetch all the values for our field: + $filter['options'] = $wpdb->get_col( + $wpdb->prepare( + " + SELECT DISTINCT post_author + FROM {$wpdb->posts} + WHERE post_type = %s + ", + $this->cpt->post_type + ) + ); + } elseif ( is_callable( $filter['options'] ) ) { + $filter['options'] = call_user_func( $filter['options'] ); + } + + if ( empty( $filter['options'] ) ) { + continue; + } + + # Output a dropdown: + wp_dropdown_users( + [ + 'id' => $id, + 'include' => $filter['options'], + 'name' => 'author', + 'option_none_value' => '0', + 'selected' => (int) $value, + 'show_option_none' => $filter['title'], + ] + ); + } + } + } + + /** + * Adds our filter names to the public query vars. + * + * @param array $vars Public query variables + * @return array Updated public query variables + */ + public function add_query_vars( array $vars ): array { + /** @var array */ + $filters = array_keys( $this->args['admin_filters'] ); + + return array_merge( $vars, $filters ); + } + + /** + * Filters posts by our custom admin filters. + * + * @param WP_Query $wp_query A `WP_Query` object + */ + public function maybe_filter( WP_Query $wp_query ): void { + if ( empty( $wp_query->query['post_type'] ) || ! in_array( $this->cpt->post_type, (array) $wp_query->query['post_type'], true ) ) { + return; + } + + $vars = PostType::get_filter_vars( $wp_query->query, $this->cpt->args['admin_filters'], $this->cpt->post_type ); + + if ( empty( $vars ) ) { + return; + } + + foreach ( $vars as $key => $value ) { + if ( is_array( $value ) ) { + $query = $wp_query->get( $key ); + if ( empty( $query ) ) { + $query = []; + } + $value = array_merge( $query, $value ); + } + $wp_query->set( $key, $value ); + } + } + + /** + * Sets the relevant query vars for sorting posts by our admin sortables. + * + * @param WP_Query $wp_query The current `WP_Query` object. + */ + public function maybe_sort_by_fields( WP_Query $wp_query ): void { + if ( empty( $wp_query->query['post_type'] ) || ! in_array( $this->cpt->post_type, (array) $wp_query->query['post_type'], true ) ) { + return; + } + + $sort = PostType::get_sort_field_vars( $wp_query->query, $this->cpt->args['admin_cols'] ); + + if ( empty( $sort ) ) { + return; + } + + foreach ( $sort as $key => $value ) { + $wp_query->set( $key, $value ); + } + } + + /** + * Filters the query's SQL clauses so we can sort posts by taxonomy terms. + * + * @param array $clauses The current query's SQL clauses. + * @param WP_Query $wp_query The current `WP_Query` object. + * @return array The updated SQL clauses. + */ + public function maybe_sort_by_taxonomy( array $clauses, WP_Query $wp_query ): array { + if ( empty( $wp_query->query['post_type'] ) || ! in_array( $this->cpt->post_type, (array) $wp_query->query['post_type'], true ) ) { + return $clauses; + } + + $sort = PostType::get_sort_taxonomy_clauses( $clauses, $wp_query->query, $this->cpt->args['admin_cols'] ); + + if ( empty( $sort ) ) { + return $clauses; + } + + return array_merge( $clauses, $sort ); + } + + /** + * Adds our post type to the 'At a Glance' widget on the dashboard. + * + * @param array $items Array of items to display on the widget. + * @return array Updated array of items. + */ + public function glance_items( array $items ): array { + /** @var \WP_Post_Type */ + $pto = get_post_type_object( $this->cpt->post_type ); + + if ( ! current_user_can( $pto->cap->edit_posts ) ) { + return $items; + } + if ( $pto->_builtin ) { + return $items; + } + + # Get the labels and format the counts: + /** @var \stdClass */ + $count = wp_count_posts( $this->cpt->post_type ); + $text = self::n( $pto->labels->singular_name, $pto->labels->name, (int) $count->publish ); + $num = number_format_i18n( $count->publish ); + + # This is absolutely not localisable. WordPress 3.8 didn't add a new post type label. + $url = add_query_arg( + [ + 'post_type' => $this->cpt->post_type, + ], + admin_url( 'edit.php' ) + ); + $class = 'cpt-' . $this->cpt->post_type . '-count'; + $text = '' . esc_html( $num . ' ' . $text ) . ''; + $css = <<<'ICONCSS' + +ICONCSS; + + // Add styling to display the dashicon. This isn't possible without additional CSS rules. + // https://core.trac.wordpress.org/ticket/33714 + // https://github.com/WordPress/dashicons/blob/master/codepoints.json + if ( is_string( $pto->menu_icon ) && 0 === strpos( $pto->menu_icon, 'dashicons-' ) ) { + $contents = file_get_contents( __DIR__ . '/dashicons-codepoints.json' ); + $codepoints = json_decode( $contents ?: '', true ); + $unprefixed = str_replace( 'dashicons-', '', $pto->menu_icon ); + + if ( isset( $codepoints[ $unprefixed ] ) ) { + $text .= sprintf( + $css, + esc_html( $class ), + esc_html( dechex( $codepoints[ $unprefixed ] ) ) + ); + } + } + + # Go! + $items[] = $text; + + return $items; + } + + /** + * Adds our post type to the 'Recently Published' section on the dashboard. + * + * @param array $query_args Array of query args for the widget. + * @return array Updated array of query args. + */ + public function dashboard_activity( array $query_args ): array { + $query_args['post_type'] = (array) $query_args['post_type']; + + $query_args['post_type'][] = $this->cpt->post_type; + + return $query_args; + } + + /** + * Adds our post type updated messages. + * + * The messages are as follows: + * + * 1 => "Post updated. {View Post}" + * 2 => "Custom field updated." + * 3 => "Custom field deleted." + * 4 => "Post updated." + * 5 => "Post restored to revision from [date]." + * 6 => "Post published. {View post}" + * 7 => "Post saved." + * 8 => "Post submitted. {Preview post}" + * 9 => "Post scheduled for: [date]. {Preview post}" + * 10 => "Post draft updated. {Preview post}" + * + * @param array> $messages An array of post updated message arrays keyed by post type. + * @return array> Updated array of post updated messages. + */ + public function post_updated_messages( array $messages ): array { + global $post; + + $pto = get_post_type_object( $this->cpt->post_type ); + + if ( ! ( $pto instanceof \WP_Post_Type ) ) { + return $messages; + } + + $messages[ $this->cpt->post_type ] = [ + 1 => sprintf( + ( $pto->publicly_queryable ? '%1$s updated. View %3$s' : '%1$s updated.' ), + esc_html( $this->cpt->post_singular ), + esc_url( get_permalink( $post ) ), + esc_html( $this->cpt->post_singular_low ) + ), + 2 => 'Custom field updated.', + 3 => 'Custom field deleted.', + 4 => sprintf( + '%s updated.', + esc_html( $this->cpt->post_singular ) + ), + 5 => isset( $_GET['revision'] ) ? sprintf( + '%1$s restored to revision from %2$s', + esc_html( $this->cpt->post_singular ), + wp_post_revision_title( intval( $_GET['revision'] ), false ) + ) : false, + 6 => sprintf( + ( $pto->publicly_queryable ? '%1$s published. View %3$s' : '%1$s published.' ), + esc_html( $this->cpt->post_singular ), + esc_url( get_permalink( $post ) ), + esc_html( $this->cpt->post_singular_low ) + ), + 7 => sprintf( + '%s saved.', + esc_html( $this->cpt->post_singular ) + ), + 8 => sprintf( + ( $pto->publicly_queryable ? '%1$s submitted. Preview %3$s' : '%1$s submitted.' ), + esc_html( $this->cpt->post_singular ), + esc_url( get_preview_post_link( $post ) ), + esc_html( $this->cpt->post_singular_low ) + ), + 9 => sprintf( + ( $pto->publicly_queryable ? '%1$s scheduled for: %2$s. Preview %4$s' : '%1$s scheduled for: %2$s.' ), + esc_html( $this->cpt->post_singular ), + esc_html( date_i18n( 'M j, Y @ G:i', strtotime( $post->post_date ) ) ), + esc_url( get_permalink( $post ) ), + esc_html( $this->cpt->post_singular_low ) + ), + 10 => sprintf( + ( $pto->publicly_queryable ? '%1$s draft updated. Preview %3$s' : '%1$s draft updated.' ), + esc_html( $this->cpt->post_singular ), + esc_url( get_preview_post_link( $post ) ), + esc_html( $this->cpt->post_singular_low ) + ), + ]; + + return $messages; + } + + /** + * Adds our bulk post type updated messages. + * + * The messages are as follows: + * + * - updated => "Post updated." | "[n] posts updated." + * - locked => "Post not updated, somebody is editing it." | "[n] posts not updated, somebody is editing them." + * - deleted => "Post permanently deleted." | "[n] posts permanently deleted." + * - trashed => "Post moved to the trash." | "[n] posts moved to the trash." + * - untrashed => "Post restored from the trash." | "[n] posts restored from the trash." + * + * @param array> $messages An array of bulk post updated message arrays keyed by post type. + * @param array $counts An array of counts for each key in `$messages`. + * @return array> Updated array of bulk post updated messages. + */ + public function bulk_post_updated_messages( array $messages, array $counts ): array { + $messages[ $this->cpt->post_type ] = [ + 'updated' => sprintf( + self::n( '%2$s updated.', '%1$s %3$s updated.', $counts['updated'] ), + esc_html( number_format_i18n( $counts['updated'] ) ), + esc_html( $this->cpt->post_singular ), + esc_html( $this->cpt->post_plural_low ) + ), + 'locked' => sprintf( + self::n( '%2$s not updated, somebody is editing it.', '%1$s %3$s not updated, somebody is editing them.', $counts['locked'] ), + esc_html( number_format_i18n( $counts['locked'] ) ), + esc_html( $this->cpt->post_singular ), + esc_html( $this->cpt->post_plural_low ) + ), + 'deleted' => sprintf( + self::n( '%2$s permanently deleted.', '%1$s %3$s permanently deleted.', $counts['deleted'] ), + esc_html( number_format_i18n( $counts['deleted'] ) ), + esc_html( $this->cpt->post_singular ), + esc_html( $this->cpt->post_plural_low ) + ), + 'trashed' => sprintf( + self::n( '%2$s moved to the trash.', '%1$s %3$s moved to the trash.', $counts['trashed'] ), + esc_html( number_format_i18n( $counts['trashed'] ) ), + esc_html( $this->cpt->post_singular ), + esc_html( $this->cpt->post_plural_low ) + ), + 'untrashed' => sprintf( + self::n( '%2$s restored from the trash.', '%1$s %3$s restored from the trash.', $counts['untrashed'] ), + esc_html( number_format_i18n( $counts['untrashed'] ) ), + esc_html( $this->cpt->post_singular ), + esc_html( $this->cpt->post_plural_low ) + ), + ]; + + return $messages; + } + + /** + * Adds our custom columns to the list of sortable columns. + * + * @param array $cols Array of sortable columns keyed by the column ID. + * @return array Updated array of sortable columns. + */ + public function sortables( array $cols ): array { + $admin_cols = $this->args['admin_cols']; + + /** @var array $admin_cols */ + foreach ( $admin_cols as $id => $col ) { + if ( ! is_array( $col ) ) { + continue; + } + if ( isset( $col['sortable'] ) && ! $col['sortable'] ) { + continue; + } + if ( isset( $col['meta_key'] ) || isset( $col['taxonomy'] ) || isset( $col['post_field'] ) ) { + $cols[ $id ] = $id; + } + } + + return $cols; + } + + /** + * Adds columns to the admin screen for this post type. + * + * @link https://github.com/johnbillion/extended-cpts/wiki/Admin-columns + * + * @param array $cols Associative array of columns + * @return array Updated array of columns + */ + public function cols( array $cols ): array { + // This function gets called multiple times, so let's cache it for efficiency: + if ( isset( $this->the_cols ) ) { + return $this->the_cols; + } + + $new_cols = []; + $keep = [ + 'cb', + 'title', + ]; + + # Add existing columns we want to keep: + foreach ( $cols as $id => $title ) { + if ( in_array( $id, $keep, true ) && ! isset( $this->args['admin_cols'][ $id ] ) ) { + $new_cols[ $id ] = $title; + } + } + + /** @var array */ + $admin_cols = array_filter( $this->args['admin_cols'] ); + + # Add our custom columns: + foreach ( $admin_cols as $id => $col ) { + if ( is_string( $col ) && isset( $cols[ $col ] ) ) { + # Existing (ie. built-in) column with id as the value + $new_cols[ $col ] = $cols[ $col ]; + } elseif ( is_string( $col ) && isset( $cols[ $id ] ) ) { + # Existing (ie. built-in) column with id as the key and title as the value + $new_cols[ $id ] = esc_html( $col ); + } elseif ( 'author' === $col ) { + # Automatic support for Co-Authors Plus plugin and special case for + # displaying author column when the post type doesn't support 'author' + if ( class_exists( 'coauthors_plus' ) ) { + $k = 'coauthors'; + } else { + $k = 'author'; + } + $new_cols[ $k ] = esc_html__( 'Author', 'extended-cpts' ); + } elseif ( is_array( $col ) ) { + if ( isset( $col['cap'] ) && ! current_user_can( $col['cap'] ) ) { + continue; + } + if ( isset( $col['connection'] ) && ! function_exists( 'p2p_type' ) ) { + continue; + } + + if ( isset( $col['title_cb'] ) ) { + $new_cols[ $id ] = call_user_func( $col['title_cb'], $col ); + } else { + $title = esc_html( $this->get_item_title( $col, $id ) ); + + if ( isset( $col['title_icon'] ) ) { + $title = sprintf( + '%s', + esc_attr( $col['title_icon'] ), + $title + ); + } + + $new_cols[ $id ] = $title; + } + } + } + + # Re-add any custom columns: + $custom = array_diff_key( $cols, $this->_cols ); + $new_cols = array_merge( $new_cols, $custom ); + + $this->the_cols = $new_cols; + return $this->the_cols; + } + + /** + * Output the column data for our custom columns. + * + * @param string $col The column name. + * @param int $post_id The post ID. + */ + public function col( string $col, int $post_id ): void { + # Shorthand: + $c = $this->args['admin_cols']; + + # We're only interested in our custom columns: + $custom_cols = array_filter( array_keys( $c ) ); + + if ( ! in_array( $col, $custom_cols, true ) ) { + return; + } + + if ( isset( $c[ $col ]['post_cap'] ) && ! current_user_can( $c[ $col ]['post_cap'], get_the_ID() ) ) { + return; + } + + $post = get_post( $post_id ); + + if ( ! $post ) { + return; + } + + if ( ! isset( $c[ $col ]['link'] ) ) { + $c[ $col ]['link'] = 'list'; + } + + if ( isset( $c[ $col ]['function'] ) ) { + call_user_func( $c[ $col ]['function'], $post ); + } elseif ( isset( $c[ $col ]['meta_key'] ) ) { + $this->col_post_meta( $post, $c[ $col ]['meta_key'], $c[ $col ] ); + } elseif ( isset( $c[ $col ]['taxonomy'] ) ) { + $this->col_taxonomy( $post, $c[ $col ]['taxonomy'], $c[ $col ] ); + } elseif ( isset( $c[ $col ]['post_field'] ) ) { + $this->col_post_field( $post, $c[ $col ]['post_field'], $c[ $col ] ); + } elseif ( isset( $c[ $col ]['featured_image'] ) ) { + $this->col_featured_image( $post, $c[ $col ]['featured_image'], $c[ $col ] ); + } elseif ( isset( $c[ $col ]['connection'] ) ) { + $this->col_connection( $post, $c[ $col ]['connection'], $c[ $col ] ); + } + } + + /** + * Outputs column data for a post meta field. + * + * @param WP_Post $post The post object. + * @param string $meta_key The post meta key. + * @param array $args Array of arguments for this field. + */ + public function col_post_meta( WP_Post $post, string $meta_key, array $args ): void { + $vals = get_post_meta( $post->ID, $meta_key, false ); + $echo = []; + + sort( $vals ); + + if ( isset( $args['date_format'] ) ) { + if ( true === $args['date_format'] ) { + $args['date_format'] = get_option( 'date_format' ); + } + + foreach ( $vals as $val ) { + try { + $val_time = ( new DateTime( '@' . $val ) )->format( 'U' ); + } catch ( Exception $e ) { + $val_time = strtotime( $val ); + } + + if ( false !== $val_time ) { + $val = $val_time; + } + + if ( is_numeric( $val ) ) { + $echo[] = date_i18n( $args['date_format'], (int) $val ); + } elseif ( ! empty( $val ) ) { + $echo[] = mysql2date( $args['date_format'], $val ); + } + } + } else { + foreach ( $vals as $val ) { + + if ( ! empty( $val ) || ( '0' === $val ) ) { + $echo[] = $val; + } + } + } + + if ( empty( $echo ) ) { + echo '—'; + } else { + echo esc_html( implode( ', ', $echo ) ); + } + } + + /** + * Outputs column data for a taxonomy's term names. + * + * @param WP_Post $post The post object. + * @param string $taxonomy The taxonomy name. + * @param array $args Array of arguments for this field. + */ + public function col_taxonomy( WP_Post $post, string $taxonomy, array $args ): void { + $terms = get_the_terms( $post, $taxonomy ); + $tax = get_taxonomy( $taxonomy ); + + if ( is_wp_error( $terms ) ) { + echo esc_html( $terms->get_error_message() ); + return; + } + + if ( ! $tax ) { + return; + } + + if ( empty( $terms ) ) { + printf( + '%s', + esc_html( $tax->labels->no_terms ) + ); + return; + } + + $out = []; + + foreach ( $terms as $term ) { + if ( $args['link'] ) { + switch ( $args['link'] ) { + + case 'view': + if ( $tax->public ) { + // https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues/1096 + // @codingStandardsIgnoreStart + $out[] = sprintf( + '%2$s', + esc_url( get_term_link( $term ) ), + esc_html( $term->name ) + ); + // @codingStandardsIgnoreEnd + } else { + $out[] = esc_html( $term->name ); + } + break; + + case 'edit': + if ( current_user_can( $tax->cap->edit_terms ) ) { + $out[] = sprintf( + '%2$s', + esc_url( get_edit_term_link( $term->term_id, $taxonomy, $post->post_type ) ), + esc_html( $term->name ) + ); + } else { + $out[] = esc_html( $term->name ); + } + break; + + case 'list': + $link = add_query_arg( + [ + 'post_type' => $post->post_type, + $taxonomy => $term->slug, + ], + admin_url( 'edit.php' ) + ); + $out[] = sprintf( + '%2$s', + esc_url( $link ), + esc_html( $term->name ) + ); + break; + + } + } else { + $out[] = esc_html( $term->name ); + } + } + + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo implode( ', ', $out ); + } + + /** + * Outputs column data for a post field. + * + * @param WP_Post $post The post object. + * @param string $field The post field. + * @param array $args Array of arguments for this field. + */ + public function col_post_field( WP_Post $post, string $field, array $args ): void { + switch ( $field ) { + + case 'post_date': + case 'post_date_gmt': + case 'post_modified': + case 'post_modified_gmt': + if ( '0000-00-00 00:00:00' !== get_post_field( $field, $post ) ) { + if ( ! isset( $args['date_format'] ) ) { + $args['date_format'] = get_option( 'date_format' ); + } + echo esc_html( mysql2date( $args['date_format'], get_post_field( $field, $post ) ) ); + } + break; + + case 'post_status': + /** @var \stdClass|null */ + $status = get_post_status_object( $post->post_status ); + if ( $status ) { + echo esc_html( $status->label ); + } + break; + + case 'post_author': + $author = get_the_author(); + + if ( $author ) { + echo esc_html( $author ); + } + break; + + case 'post_title': + echo esc_html( get_the_title() ); + break; + + case 'post_excerpt': + echo esc_html( get_the_excerpt() ); + break; + + default: + echo esc_html( get_post_field( $field, $post ) ); + break; + + } + } + + /** + * Outputs column data for a post's featured image. + * + * @param WP_Post $post The post object. + * @param string $image_size The image size. + * @param array $args Array of `width` and `height` attributes for the image. + */ + public function col_featured_image( WP_Post $post, string $image_size, array $args ): void { + if ( ! function_exists( 'has_post_thumbnail' ) ) { + return; + } + + if ( isset( $args['width'] ) ) { + $width = is_numeric( $args['width'] ) ? sprintf( '%dpx', $args['width'] ) : $args['width']; + } else { + $width = 'auto'; + } + + if ( isset( $args['height'] ) ) { + $height = is_numeric( $args['height'] ) ? sprintf( '%dpx', $args['height'] ) : $args['height']; + } else { + $height = 'auto'; + } + + $image_atts = [ + 'style' => esc_attr( + sprintf( + 'width:%1$s;height:%2$s', + $width, + $height + ) + ), + 'title' => '', + ]; + + if ( has_post_thumbnail() ) { + the_post_thumbnail( $image_size, $image_atts ); + } + } + + /** + * Outputs column data for a Posts 2 Posts connection. + * + * @param WP_Post $post_object The post object. + * @param string $connection The ID of the connection type. + * @param array $args Array of arguments for a given connection type. + */ + public function col_connection( WP_Post $post_object, string $connection, array $args ): void { + global $post; + + if ( ! function_exists( 'p2p_type' ) ) { + return; + } + + if ( ! $this->p2p_connection_exists( $connection ) ) { + echo esc_html( + sprintf( + /* translators: %s: The ID of the Posts 2 Posts connection type */ + __( 'Invalid connection type: %s', 'extended-cpts' ), + $connection + ) + ); + return; + } + + $_post = $post; + $meta = []; + $out = []; + $field = 'connected_' . $connection; + + if ( isset( $args['field'] ) && isset( $args['value'] ) ) { + $meta = [ + 'connected_meta' => [ + $args['field'] => $args['value'], + ], + ]; + $field .= sanitize_title( '_' . $args['field'] . '_' . $args['value'] ); + } + + if ( ! isset( $post_object->$field ) ) { + $type = p2p_type( $connection ); + if ( $type ) { + $type->each_connected( [ $post_object ], $meta, $field ); + } else { + echo esc_html( + sprintf( + /* translators: %s: The ID of the Posts 2 Posts connection type */ + __( 'Invalid connection type: %s', 'extended-cpts' ), + $connection + ) + ); + return; + } + } + + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + foreach ( $post_object->$field as $post ) { + setup_postdata( $post ); + + /** @var \WP_Post_Type */ + $pto = get_post_type_object( $post->post_type ); + /** @var \stdClass */ + $pso = get_post_status_object( $post->post_status ); + + if ( $pso->protected && ! current_user_can( 'edit_post', $post->ID ) ) { + continue; + } + if ( 'trash' === $post->post_status ) { + continue; + } + + if ( $args['link'] ) { + switch ( $args['link'] ) { + + case 'view': + if ( $pto->public ) { + if ( $pso->protected ) { + $out[] = sprintf( + '%2$s', + esc_url( get_preview_post_link() ), + esc_html( get_the_title() ) + ); + } else { + $out[] = sprintf( + '%2$s', + esc_url( get_permalink() ), + esc_html( get_the_title() ) + ); + } + } else { + $out[] = esc_html( get_the_title() ); + } + break; + + case 'edit': + if ( current_user_can( 'edit_post', $post->ID ) ) { + $out[] = sprintf( + '%2$s', + esc_url( get_edit_post_link() ), + esc_html( get_the_title() ) + ); + } else { + $out[] = esc_html( get_the_title() ); + } + break; + + case 'list': + $link = add_query_arg( + array_merge( + [ + 'post_type' => $_post->post_type, + 'connected_type' => $connection, + 'connected_items' => $post->ID, + ], + $meta + ), + admin_url( 'edit.php' ) + ); + $out[] = sprintf( + '%2$s', + esc_url( $link ), + esc_html( get_the_title() ) + ); + break; + + } + } else { + $out[] = esc_html( get_the_title() ); + } + } + + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $post = $_post; + + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo implode( ', ', $out ); + } + + /** + * Removes the Quick Edit link from the post row actions. + * + * @param array $actions Array of post actions. + * @param WP_Post $post The current post object. + * @return array Array of updated post actions. + */ + public function remove_quick_edit_action( array $actions, WP_Post $post ): array { + if ( $this->cpt->post_type !== $post->post_type ) { + return $actions; + } + + unset( $actions['inline'], $actions['inline hide-if-no-js'] ); + + return $actions; + } + + /** + * Removes the Quick Edit link from the bulk actions menu. + * + * @param array $actions Array of bulk actions. + * @return array Array of updated bulk actions. + */ + public function remove_quick_edit_menu( array $actions ): array { + unset( $actions['edit'] ); + + return $actions; + } + + /** + * Logs the default columns so we don't remove any custom columns added by other plugins. + * + * @param array $cols The default columns for this post type screen. + * @return array The default columns for this post type screen. + */ + public function _log_default_cols( array $cols ): array { + $this->_cols = $cols; + + return $this->_cols; + } + + /** + * A non-localised version of _n() + * + * @param string $single The text that will be used if $number is 1. + * @param string $plural The text that will be used if $number is not 1. + * @param int $number The number to compare against to use either `$single` or `$plural`. + * @return string Either `$single` or `$plural` text. + */ + protected static function n( string $single, string $plural, int $number ): string { + return ( 1 === intval( $number ) ) ? $single : $plural; + } + + /** + * Returns a sensible title for the current item (usually the arguments array for a column) + * + * @param array $item An array of arguments. + * @param string $fallback Fallback item title. + * @return string The item title. + */ + protected function get_item_title( array $item, string $fallback = '' ): string { + if ( isset( $item['title'] ) ) { + return $item['title']; + } elseif ( isset( $item['taxonomy'] ) ) { + $tax = get_taxonomy( $item['taxonomy'] ); + if ( $tax ) { + if ( ! empty( $tax->exclusive ) ) { + return $tax->labels->singular_name; + } else { + return $tax->labels->name; + } + } else { + return $item['taxonomy']; + } + } elseif ( isset( $item['post_field'] ) ) { + return ucwords( + trim( + str_replace( + [ + 'post_', + '_', + ], + ' ', + $item['post_field'] + ) + ) + ); + } elseif ( isset( $item['meta_key'] ) ) { + return ucwords( + trim( + str_replace( + [ + '_', + '-', + ], + ' ', + $item['meta_key'] + ) + ) + ); + } elseif ( isset( $item['connection'] ) && isset( $item['field'] ) && isset( $item['value'] ) ) { + $fallback = ucwords( + trim( + str_replace( + [ + '_', + '-', + ], + ' ', + $item['value'] + ) + ) + ); + + if ( ! function_exists( 'p2p_type' ) || ! $this->p2p_connection_exists( $item['connection'] ) ) { + return $fallback; + } + + $ctype = p2p_type( $item['connection'] ); + if ( ! $ctype ) { + return $fallback; + } + + if ( isset( $ctype->fields[ $item['field'] ]['values'][ $item['value'] ] ) ) { + if ( '' === trim( $ctype->fields[ $item['field'] ]['values'][ $item['value'] ] ) ) { + return $ctype->fields[ $item['field'] ]['title']; + } else { + return $ctype->fields[ $item['field'] ]['values'][ $item['value'] ]; + } + } + + return $fallback; + } elseif ( isset( $item['connection'] ) ) { + if ( function_exists( 'p2p_type' ) && $this->p2p_connection_exists( $item['connection'] ) ) { + $ctype = p2p_type( $item['connection'] ); + if ( $ctype ) { + $other = ( 'from' === $ctype->direction_from_types( 'post', $this->cpt->post_type ) ) ? 'to' : 'from'; + return $ctype->side[ $other ]->get_title(); + } + } + return $item['connection']; + } + + return $fallback; + } + + /** + * Checks if a certain Posts 2 Posts connection exists. + * + * This is just a caching wrapper for `p2p_connection_exists()`, which performs a + * database query on every call. + * + * @param string $connection A connection type. + * @return bool Whether the connection exists. + */ + protected function p2p_connection_exists( string $connection ): bool { + if ( ! isset( $this->connection_exists[ $connection ] ) ) { + $this->connection_exists[ $connection ] = p2p_connection_exists( $connection ); + } + + return $this->connection_exists[ $connection ]; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/PostTypeRewriteTesting.php b/vendor/johnbillion/extended-cpts/src/PostTypeRewriteTesting.php new file mode 100644 index 0000000..2521c6a --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/PostTypeRewriteTesting.php @@ -0,0 +1,66 @@ +cpt = $cpt; + } + + /** + * @return array> + */ + public function get_tests(): array { + global $wp_rewrite; + + /** @var \WP_Rewrite $wp_rewrite */ + + if ( ! $wp_rewrite->using_permalinks() ) { + return []; + } + + if ( ! isset( $wp_rewrite->extra_permastructs[ $this->cpt->post_type ] ) ) { + return []; + } + + $struct = $wp_rewrite->extra_permastructs[ $this->cpt->post_type ]; + /** @var \WP_Post_Type */ + $pto = get_post_type_object( $this->cpt->post_type ); + $name = sprintf( '%s (%s)', $pto->labels->name, $this->cpt->post_type ); + $additional = []; + + // Post type archive rewrites are generated separately. See the `has_archive` handling in `register_post_type()`. + if ( $pto->has_archive && $pto->rewrite ) { + $archive_slug = ( true === $pto->has_archive ) ? $pto->rewrite['slug'] : $pto->has_archive; + + if ( $pto->rewrite['with_front'] ) { + $archive_slug = substr( $wp_rewrite->front, 1 ) . $archive_slug; + } else { + $archive_slug = $wp_rewrite->root . $archive_slug; + } + + $additional[ "{$archive_slug}/?$" ] = "index.php?post_type={$this->cpt->post_type}"; + + if ( $pto->rewrite['feeds'] && $wp_rewrite->feeds ) { + $feeds = '(' . trim( implode( '|', $wp_rewrite->feeds ) ) . ')'; + $additional[ "{$archive_slug}/feed/{$feeds}/?$" ] = "index.php?post_type={$this->cpt->post_type}" . '&feed=$matches[1]'; + $additional[ "{$archive_slug}/{$feeds}/?$" ] = "index.php?post_type={$this->cpt->post_type}" . '&feed=$matches[1]'; + } + if ( $pto->rewrite['pages'] ) { + $additional[ "{$archive_slug}/{$wp_rewrite->pagination_base}/([0-9]{1,})/?$" ] = "index.php?post_type={$this->cpt->post_type}" . '&paged=$matches[1]'; + } + } + + return [ + $name => $this->get_rewrites( $struct, $additional ), + ]; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/Taxonomy.php b/vendor/johnbillion/extended-cpts/src/Taxonomy.php new file mode 100644 index 0000000..3e8b958 --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/Taxonomy.php @@ -0,0 +1,273 @@ + + */ + protected array $defaults = [ + 'public' => true, + 'show_ui' => true, + 'hierarchical' => true, + 'query_var' => true, + 'exclusive' => false, # Custom arg + 'allow_hierarchy' => false, # Custom arg + ]; + + public string $taxonomy; + + /** + * @var array + */ + public array $object_type; + + public string $tax_slug; + + public string $tax_singular; + + public string $tax_plural; + + public string $tax_singular_low; + + public string $tax_plural_low; + + /** + * @var array + */ + public array $args; + + /** + * Class constructor. + * + * @see register_extended_taxonomy() + * + * @param string $taxonomy The taxonomy name. + * @param array $object_type Names of the object types for the taxonomy. + * @param array $args Optional. The taxonomy arguments. + * @param array $names Optional. An associative array of the plural, singular, and slug names. + * @phpstan-param array{ + * plural?: string, + * singular?: string, + * slug?: string, + * } $names + */ + public function __construct( string $taxonomy, array $object_type, array $args = [], array $names = [] ) { + /** + * Filter the arguments for a taxonomy. + * + * @since 4.4.1 + * + * @param array $args The taxonomy arguments. + * @param string $taxonomy The taxonomy name. + */ + $args = apply_filters( 'ext-taxos/args', $args, $taxonomy ); + + /** + * Filter the arguments for this taxonomy. + * + * @since 2.0.0 + * + * @param array $args The taxonomy arguments. + */ + $args = apply_filters( "ext-taxos/{$taxonomy}/args", $args ); + + /** + * Filter the plural, singular, and slug for a taxonomy. + * + * @since 4.4.1 + * + * @param array $names The plural, singular, and slug names (if any were specified). + * @param string $taxonomy The taxonomy name. + */ + $names = apply_filters( 'ext-taxos/names', $names, $taxonomy ); + + /** + * Filter the plural, singular, and slug for this taxonomy. + * + * @since 2.0.0 + * + * @param array $names The plural, singular, and slug names (if any were specified). + */ + $names = apply_filters( "ext-taxos/{$taxonomy}/names", $names ); + + if ( isset( $names['singular'] ) ) { + $this->tax_singular = $names['singular']; + } else { + $this->tax_singular = ucwords( str_replace( [ '-', '_' ], ' ', $taxonomy ) ); + } + + if ( isset( $names['slug'] ) ) { + $this->tax_slug = $names['slug']; + } elseif ( isset( $names['plural'] ) ) { + $this->tax_slug = $names['plural']; + } else { + $this->tax_slug = $taxonomy . 's'; + } + + if ( isset( $names['plural'] ) ) { + $this->tax_plural = $names['plural']; + } else { + $this->tax_plural = ucwords( str_replace( [ '-', '_' ], ' ', $this->tax_slug ) ); + } + + $this->object_type = $object_type; + $this->taxonomy = strtolower( $taxonomy ); + $this->tax_slug = strtolower( $this->tax_slug ); + + # Build our base taxonomy names: + # Lower-casing is not forced if the name looks like an initialism, eg. FAQ. + if ( ! preg_match( '/[A-Z]{2,}/', $this->tax_singular ) ) { + $this->tax_singular_low = strtolower( $this->tax_singular ); + } else { + $this->tax_singular_low = $this->tax_singular; + } + + if ( ! preg_match( '/[A-Z]{2,}/', $this->tax_plural ) ) { + $this->tax_plural_low = strtolower( $this->tax_plural ); + } else { + $this->tax_plural_low = $this->tax_plural; + } + + # Build our labels: + $this->defaults['labels'] = [ + 'menu_name' => $this->tax_plural, + 'name' => $this->tax_plural, + 'singular_name' => $this->tax_singular, + 'name_admin_bar' => $this->tax_singular, + 'search_items' => sprintf( 'Search %s', $this->tax_plural ), + 'popular_items' => sprintf( 'Popular %s', $this->tax_plural ), + 'all_items' => sprintf( 'All %s', $this->tax_plural ), + 'archives' => sprintf( '%s Archives', $this->tax_plural ), + 'parent_item' => sprintf( 'Parent %s', $this->tax_singular ), + 'parent_item_colon' => sprintf( 'Parent %s:', $this->tax_singular ), + 'edit_item' => sprintf( 'Edit %s', $this->tax_singular ), + 'view_item' => sprintf( 'View %s', $this->tax_singular ), + 'update_item' => sprintf( 'Update %s', $this->tax_singular ), + 'add_new_item' => sprintf( 'Add New %s', $this->tax_singular ), + 'new_item_name' => sprintf( 'New %s Name', $this->tax_singular ), + 'separate_items_with_commas' => sprintf( 'Separate %s with commas', $this->tax_plural_low ), + 'add_or_remove_items' => sprintf( 'Add or remove %s', $this->tax_plural_low ), + 'choose_from_most_used' => sprintf( 'Choose from most used %s', $this->tax_plural_low ), + 'not_found' => sprintf( 'No %s found', $this->tax_plural_low ), + 'no_terms' => sprintf( 'No %s', $this->tax_plural_low ), + 'filter_by_item' => sprintf( 'Filter by %s', $this->tax_singular_low ), + 'items_list_navigation' => sprintf( '%s list navigation', $this->tax_plural ), + 'items_list' => sprintf( '%s list', $this->tax_plural ), + 'most_used' => 'Most Used', + 'back_to_items' => sprintf( '← Back to %s', $this->tax_plural ), + 'item_link' => sprintf( '%s Link', $this->tax_singular ), + 'item_link_description' => sprintf( 'A link to a %s.', $this->tax_singular_low ), + 'template_name' => sprintf( '%s Archives', $this->tax_singular ), + 'no_item' => sprintf( 'No %s', $this->tax_singular_low ), # Custom label + 'filter_by' => sprintf( 'Filter by %s', $this->tax_singular_low ), # Custom label + ]; + + # Only set rewrites if we need them + if ( isset( $args['public'] ) && ! $args['public'] ) { + $this->defaults['rewrite'] = false; + } else { + $this->defaults['rewrite'] = [ + 'slug' => $this->tax_slug, + 'with_front' => false, + 'hierarchical' => isset( $args['allow_hierarchy'] ) ? $args['allow_hierarchy'] : $this->defaults['allow_hierarchy'], + ]; + } + + # Merge our args with the defaults: + $this->args = array_merge( $this->defaults, $args ); + + # This allows the 'labels' arg to contain some, none or all labels: + if ( isset( $args['labels'] ) ) { + $this->args['labels'] = array_merge( $this->defaults['labels'], $args['labels'] ); + } + } + + /** + * Initialise the taxonomy by adding the necessary actions and filters. + */ + public function init(): void { + # Rewrite testing: + if ( $this->args['rewrite'] ) { + add_filter( 'rewrite_testing_tests', [ $this, 'rewrite_testing_tests' ], 1 ); + } + + # Register taxonomy: + $this->register_taxonomy(); + + /** + * Fired when the extended taxonomy instance is set up. + * + * @since 4.0.0 + * + * @param \ExtCPTs\Taxonomy $instance The extended taxonomy instance. + */ + do_action( "ext-taxos/{$this->taxonomy}/instance", $this ); + } + + /** + * Add our rewrite tests to the Rewrite Rule Testing tests array. + * + * @codeCoverageIgnore + * + * @param array> $tests The existing rewrite rule tests. + * @return array> Updated rewrite rule tests. + */ + public function rewrite_testing_tests( array $tests ): array { + require_once __DIR__ . '/ExtendedRewriteTesting.php'; + require_once __DIR__ . '/TaxonomyRewriteTesting.php'; + + $extended = new TaxonomyRewriteTesting( $this ); + + return array_merge( $tests, $extended->get_tests() ); + } + + /** + * Registers our taxonomy. + */ + public function register_taxonomy(): void { + if ( true === $this->args['query_var'] ) { + $query_var = $this->taxonomy; + } else { + $query_var = $this->args['query_var']; + } + + $post_types = get_post_types( + [ + 'query_var' => $query_var, + ] + ); + + if ( $query_var && count( $post_types ) ) { + trigger_error( + esc_html( + sprintf( + /* translators: %s: Taxonomy query variable name */ + __( 'Taxonomy query var "%s" clashes with a post type query var of the same name', 'extended-cpts' ), + $query_var + ) + ), + E_USER_ERROR + ); + } elseif ( in_array( $query_var, [ 'type', 'tab' ], true ) ) { + trigger_error( + esc_html( + sprintf( + /* translators: %s: Taxonomy query variable name */ + __( 'Taxonomy query var "%s" is not allowed', 'extended-cpts' ), + $query_var + ) + ), + E_USER_ERROR + ); + } else { + register_taxonomy( $this->taxonomy, $this->object_type, $this->args ); + } + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/TaxonomyAdmin.php b/vendor/johnbillion/extended-cpts/src/TaxonomyAdmin.php new file mode 100644 index 0000000..8ca450d --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/TaxonomyAdmin.php @@ -0,0 +1,663 @@ + + */ + protected array $defaults = [ + 'meta_box' => null, # Custom arg + 'dashboard_glance' => false, # Custom arg + 'checked_ontop' => null, # Custom arg + 'admin_cols' => null, # Custom arg + 'required' => false, # Custom arg + ]; + + public Taxonomy $taxo; + + /** + * @var array + */ + public array $args; + + /** + * @var array + */ + protected array $_cols; + + /** + * @var array + */ + protected ?array $the_cols = null; + + /** + * Class constructor. + * + * @param Taxonomy $taxo An extended taxonomy object. + * @param array $args Optional. The admin arguments. + */ + public function __construct( Taxonomy $taxo, array $args = [] ) { + $this->taxo = $taxo; + + # Merge our args with the defaults: + $this->args = array_merge( $this->defaults, $args ); + + # Set checked on top to false unless we're using the default meta box: + if ( null === $this->args['checked_ontop'] ) { + $this->args['checked_ontop'] = empty( $this->args['meta_box'] ); + } + } + + /** + * Initialise the admin features of the taxonomy by adding the necessary actions and filters. + */ + public function init(): void { + # Meta boxes: + if ( $this->taxo->args['exclusive'] || isset( $this->args['meta_box'] ) ) { + add_action( 'add_meta_boxes', [ $this, 'meta_boxes' ], 10, 2 ); + } + + # 'At a Glance' dashboard panels: + if ( $this->args['dashboard_glance'] ) { + add_filter( 'dashboard_glance_items', [ $this, 'glance_items' ] ); + } + + # Term updated messages: + add_filter( 'term_updated_messages', [ $this, 'term_updated_messages' ], 1 ); + + # Admin columns: + if ( $this->args['admin_cols'] ) { + add_filter( "manage_edit-{$this->taxo->taxonomy}_columns", [ $this, '_log_default_cols' ], 0 ); + add_filter( "manage_edit-{$this->taxo->taxonomy}_columns", [ $this, 'cols' ] ); + add_filter( "manage_{$this->taxo->taxonomy}_custom_column", [ $this, 'col' ], 10, 3 ); + } + + /** + * Fired when the extended taxonomy admin instance is set up. + * + * @since 5.0.0 + * + * @param \ExtCPTs\TaxonomyAdmin $instance The extended taxonomy admin instance. + */ + do_action( "ext-taxos/{$this->taxo->taxonomy}/admin-instance", $this ); + } + + /** + * Logs the default columns so we don't remove any custom columns added by other plugins. + * + * @param array $cols The default columns for this taxonomy screen. + * @return array The default columns for this taxonomy screen. + */ + public function _log_default_cols( array $cols ): array { + $this->_cols = $cols; + return $this->_cols; + } + + /** + * Add columns to the admin screen for this taxonomy. + * + * Each item in the `admin_cols` array is either a string name of an existing column, or an associative + * array of information for a custom column. + * + * Defining a custom column is easy. Just define an array which includes the column title, column + * type, and optional callback function. You can display columns for term meta or custom functions. + * + * The example below adds two columns; one which displays the value of the term's `term_updated` meta + * key, and one which calls a custom callback function: + * + * register_extended_taxonomy( 'foo', 'bar', array( + * 'admin_cols' => array( + * 'foo_updated' => array( + * 'title' => 'Updated', + * 'meta_key' => 'term_updated' + * ), + * 'foo_bar' => array( + * 'title' => 'Example', + * 'function' => 'my_custom_callback' + * ) + * ) + * ) ); + * + * That's all you need to do. The columns will handle safely outputting the data + * (escaping text, and comma-separating taxonomy terms). No more messing about with all of those + * annoyingly named column filters and actions. + * + * Each item in the `admin_cols` array must contain one of the following elements which defines the column type: + * + * - meta_key - A term meta key + * - function - The name of a callback function + * + * The value for the corresponding term meta are safely escaped and output into the column. + * + * There are a few optional elements: + * + * - title - Generated from the field if not specified. + * - function - The name of a callback function for the column (eg. `my_function`) which gets called + * instead of the built-in function for handling that column. The function is passed the term ID as + * its first parameter. + * - date_format - This is used with the `meta_key` column type. The value of the meta field will be + * treated as a timestamp if this is present. Unix and MySQL format timestamps are supported in the + * meta value. Pass in boolean true to format the date according to the 'Date Format' setting, or pass + * in a valid date formatting string (eg. `d/m/Y H:i:s`). + * - cap - A capability required in order for this column to be displayed to the current user. Defaults + * to null, meaning the column is shown to all users. + * + * Note that sortable admin columns are not yet supported. + * + * @param array $cols Associative array of columns. + * @return array Updated array of columns. + */ + public function cols( array $cols ): array { + // This function gets called multiple times, so let's cache it for efficiency: + if ( isset( $this->the_cols ) ) { + return $this->the_cols; + } + + $new_cols = []; + $keep = [ + 'cb', + 'name', + 'description', + 'slug', + ]; + + # Add existing columns we want to keep: + foreach ( $cols as $id => $title ) { + if ( in_array( $id, $keep, true ) && ! isset( $this->args['admin_cols'][ $id ] ) ) { + $new_cols[ $id ] = $title; + } + } + + /** @var array */ + $admin_cols = array_filter( $this->args['admin_cols'] ); + + # Add our custom columns: + foreach ( $admin_cols as $id => $col ) { + if ( is_string( $col ) && isset( $cols[ $col ] ) ) { + # Existing (ie. built-in) column with id as the value + $new_cols[ $col ] = $cols[ $col ]; + } elseif ( is_string( $col ) && isset( $cols[ $id ] ) ) { + # Existing (ie. built-in) column with id as the key and title as the value + $new_cols[ $id ] = esc_html( $col ); + } elseif ( is_array( $col ) ) { + if ( isset( $col['cap'] ) && ! current_user_can( $col['cap'] ) ) { + continue; + } + + if ( isset( $col['title_cb'] ) ) { + $new_cols[ $id ] = call_user_func( $col['title_cb'], $col ); + } else { + $title = esc_html( $this->get_item_title( $col, $id ) ); + + if ( isset( $col['title_icon'] ) ) { + $title = sprintf( + '%s', + esc_attr( $col['title_icon'] ), + $title + ); + } + + $new_cols[ $id ] = $title; + } + } + } + + # Re-add any custom columns: + $custom = array_diff_key( $cols, $this->_cols ); + $new_cols = array_merge( $new_cols, $custom ); + + $this->the_cols = $new_cols; + return $this->the_cols; + } + + /** + * Output the column data for our custom columns. + * + * @param string $string Blank string. + * @param string $col Name of the column. + * @param int $term_id Term ID. + * @return string Blank string. + */ + public function col( string $string, string $col, int $term_id ): string { + # Shorthand: + $c = $this->args['admin_cols']; + + # We're only interested in our custom columns: + $custom_cols = array_filter( array_keys( $c ) ); + + if ( ! in_array( $col, $custom_cols, true ) ) { + return $string; + } + + if ( isset( $c[ $col ]['function'] ) ) { + call_user_func( $c[ $col ]['function'], $term_id ); + } elseif ( isset( $c[ $col ]['meta_key'] ) ) { + $this->col_term_meta( $c[ $col ]['meta_key'], $c[ $col ], $term_id ); + } + + return $string; + } + + /** + * Output column data for a term meta field. + * + * @param string $meta_key The term meta key. + * @param array $args Array of arguments for this field. + * @param int $term_id Term ID. + */ + public function col_term_meta( string $meta_key, array $args, int $term_id ): void { + $vals = get_term_meta( $term_id, $meta_key, false ); + $echo = []; + + sort( $vals ); + + if ( isset( $args['date_format'] ) ) { + if ( true === $args['date_format'] ) { + $args['date_format'] = get_option( 'date_format' ); + } + + foreach ( $vals as $val ) { + if ( is_numeric( $val ) ) { + $echo[] = date( $args['date_format'], (int) $val ); + } elseif ( ! empty( $val ) ) { + $echo[] = mysql2date( $args['date_format'], $val ); + } + } + } else { + foreach ( $vals as $val ) { + if ( ! empty( $val ) || ( '0' === $val ) ) { + $echo[] = $val; + } + } + } + + if ( empty( $echo ) ) { + echo '—'; + } else { + echo esc_html( implode( ', ', $echo ) ); + } + } + + /** + * Returns a sensible title for the current item (usually the arguments array for a column). + * + * @param array $item An array of arguments. + * @param string $fallback Fallback item title. + * @return string The item title. + */ + protected function get_item_title( array $item, string $fallback = '' ): string { + if ( isset( $item['title'] ) ) { + return $item['title']; + } elseif ( isset( $item['meta_key'] ) ) { + return ucwords( trim( str_replace( [ '_', '-' ], ' ', $item['meta_key'] ) ) ); + } + + return $fallback; + } + + /** + * Removes the default meta box from the post editing screen and adds our custom meta box. + * + * @param string $object_type The object type (eg. the post type). + * @param mixed $object The object (eg. a WP_Post object). + */ + public function meta_boxes( string $object_type, $object ): void { + if ( ! is_a( $object, 'WP_Post' ) ) { + return; + } + + $post_type = $object_type; + $post = $object; + $taxos = get_post_taxonomies( $post ); + + if ( in_array( $this->taxo->taxonomy, $taxos, true ) ) { + /** @var WP_Taxonomy */ + $tax = get_taxonomy( $this->taxo->taxonomy ); + + # Remove default meta box from classic editor: + if ( $this->taxo->args['hierarchical'] ) { + remove_meta_box( "{$this->taxo->taxonomy}div", $post_type, 'side' ); + } else { + remove_meta_box( "tagsdiv-{$this->taxo->taxonomy}", $post_type, 'side' ); + } + + $store = version_compare( $GLOBALS['wp_version'], '6.5', '>=' ) ? 'core/editor' : 'core/edit-post'; + + # Remove default meta box from block editor: + wp_add_inline_script( + 'wp-edit-post', + sprintf( + 'wp.data.dispatch( "%s" ).removeEditorPanel( "taxonomy-panel-%s" );', + $store, + $this->taxo->taxonomy + ) + ); + + if ( ! current_user_can( $tax->cap->assign_terms ) ) { + return; + } + + if ( $this->args['meta_box'] ) { + # Set the 'meta_box' argument to the actual meta box callback function name: + if ( 'simple' === $this->args['meta_box'] ) { + if ( $this->taxo->args['exclusive'] ) { + $this->args['meta_box'] = [ $this, 'meta_box_radio' ]; + } else { + $this->args['meta_box'] = [ $this, 'meta_box_simple' ]; + } + } elseif ( 'radio' === $this->args['meta_box'] ) { + $this->taxo->args['exclusive'] = true; + $this->args['meta_box'] = [ $this, 'meta_box_radio' ]; + } elseif ( 'dropdown' === $this->args['meta_box'] ) { + $this->taxo->args['exclusive'] = true; + $this->args['meta_box'] = [ $this, 'meta_box_dropdown' ]; + } + + # Add the meta box, using the plural or singular taxonomy label where relevant: + if ( $this->taxo->args['exclusive'] ) { + add_meta_box( "{$this->taxo->taxonomy}div", $tax->labels->singular_name, $this->args['meta_box'], $post_type, 'side' ); + } else { + add_meta_box( "{$this->taxo->taxonomy}div", $tax->labels->name, $this->args['meta_box'], $post_type, 'side' ); + } + } elseif ( false !== $this->args['meta_box'] ) { + # This must be an 'exclusive' taxonomy. Add the radio meta box: + add_meta_box( "{$this->taxo->taxonomy}div", $tax->labels->singular_name, [ $this, 'meta_box_radio' ], $post_type, 'side' ); + } + } + } + + /** + * Displays the 'radio' meta box on the post editing screen. + * + * Uses the Walker\Radios class for the walker. + * + * @param WP_Post $post The post object. + * @param array $meta_box The meta box arguments. + */ + public function meta_box_radio( WP_Post $post, array $meta_box ): void { + $walker = new Walker\Radios(); + $this->do_meta_box( $post, $walker, true, 'checklist' ); + } + + /** + * Displays the 'dropdown' meta box on the post editing screen. + * + * Uses the Walker\Dropdown class for the walker. + * + * @param WP_Post $post The post object. + * @param array $meta_box The meta box arguments. + */ + public function meta_box_dropdown( WP_Post $post, array $meta_box ): void { + $walker = new Walker\Dropdown(); + $this->do_meta_box( $post, $walker, true, 'dropdown' ); + } + + /** + * Displays the 'simple' meta box on the post editing screen. + * + * @param WP_Post $post The post object. + * @param array $meta_box The meta box arguments. + */ + public function meta_box_simple( WP_Post $post, array $meta_box ): void { + $this->do_meta_box( $post ); + } + + /** + * Displays a meta box on the post editing screen. + * + * @param WP_Post $post The post object. + * @param \Walker $walker Optional. A term walker. + * @param bool $show_none Optional. Whether to include a 'none' item in the term list. Default false. + * @param string $type Optional. The taxonomy list type (checklist or dropdown). Default 'checklist'. + */ + protected function do_meta_box( WP_Post $post, ?\Walker $walker = null, bool $show_none = false, string $type = 'checklist' ): void { + $taxonomy = $this->taxo->taxonomy; + /** @var WP_Taxonomy */ + $tax = get_taxonomy( $taxonomy ); + /** @var array */ + $selected = wp_get_object_terms( + $post->ID, + $taxonomy, + [ + 'fields' => 'ids', + ] + ); + + if ( $show_none ) { + if ( isset( $tax->labels->no_item ) ) { + /** @var string $none */ + $none = $tax->labels->no_item; + } else { + $none = esc_html__( 'Not specified', 'extended-cpts' ); + } + } else { + $none = ''; + } + + /** + * Execute code before the taxonomy meta box content outputs to the page. + * + * @since 2.0.0 + * + * @param WP_Taxonomy $tax The current taxonomy object. + * @param WP_Post $post The current post object. + * @param string $type The taxonomy list type ('checklist' or 'dropdown'). + */ + do_action( 'ext-taxos/meta_box/before', $tax, $post, $type ); + + ?> +
+ + %2$s', + esc_attr( "{$taxonomy}dropdown" ), + esc_html( $tax->labels->singular_name ) + ); + + $dropdown_args = [ + 'option_none_value' => ( is_taxonomy_hierarchical( $taxonomy ) ? '-1' : '' ), + 'show_option_none' => $none, + 'hide_empty' => false, + 'hierarchical' => true, + 'show_count' => false, + 'orderby' => 'name', + 'selected' => reset( $selected ) ?: 0, + 'id' => "{$taxonomy}dropdown", + 'name' => is_taxonomy_hierarchical( $taxonomy ) ? "tax_input[{$taxonomy}][]" : "tax_input[{$taxonomy}]", + 'taxonomy' => $taxonomy, + 'required' => $this->args['required'], + ]; + + if ( $walker instanceof \Walker ) { + $dropdown_args['walker'] = $walker; + } + + wp_dropdown_categories( $dropdown_args ); + break; + + case 'checklist': + default: + ?> + + + + +
    + ID, + [ + 'taxonomy' => $taxonomy, + 'walker' => $walker, + 'selected_cats' => $selected, + 'checked_ontop' => $this->args['checked_ontop'], + ] + ); + + # Output the 'none' item: + if ( $show_none ) { + $output = ''; + $o = (object) [ + 'term_id' => 0, + 'name' => $none, + 'slug' => 'none', + ]; + if ( empty( $selected ) ) { + $_selected = [ 0 ]; + } else { + $_selected = $selected; + } + $args = [ + 'taxonomy' => $taxonomy, + 'selected_cats' => $_selected, + 'disabled' => false, + ]; + $walker->start_el( $output, $o, 1, $args ); + $walker->end_el( $output, $o, 1, $args ); + + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $output; + } + + ?> + +
+ + + +
+ $items Array of items to display on the widget. + * @return array Updated array of items. + */ + public function glance_items( array $items ): array { + /** @var WP_Taxonomy */ + $taxonomy = get_taxonomy( $this->taxo->taxonomy ); + + if ( ! current_user_can( $taxonomy->cap->manage_terms ) ) { + return $items; + } + if ( $taxonomy->_builtin ) { + return $items; + } + + # Get the labels and format the counts: + $count = wp_count_terms( + [ + 'taxonomy' => $this->taxo->taxonomy, + ] + ); + + if ( is_wp_error( $count ) ) { + return $items; + } + + $text = self::n( $taxonomy->labels->singular_name, $taxonomy->labels->name, (int) $count ); + $num = number_format_i18n( (int) $count ); + + # This is absolutely not localisable. WordPress 3.8 didn't add a new taxonomy label. + $url = add_query_arg( + [ + 'taxonomy' => $this->taxo->taxonomy, + 'post_type' => reset( $taxonomy->object_type ), + ], + admin_url( 'edit-tags.php' ) + ); + $text = '' . esc_html( $num . ' ' . $text ) . ''; + + # Go! + $items[] = $text; + + return $items; + } + + /** + * Adds our term updated messages. + * + * The messages are as follows: + * + * 1 => "Term added." + * 2 => "Term deleted." + * 3 => "Term updated." + * 4 => "Term not added." + * 5 => "Term not updated." + * 6 => "Terms deleted." + * + * @param array> $messages An array of term updated message arrays keyed by taxonomy name. + * @return array> Updated array of term updated messages. + */ + public function term_updated_messages( array $messages ): array { + $messages[ $this->taxo->taxonomy ] = [ + 1 => esc_html( sprintf( '%s added.', $this->taxo->tax_singular ) ), + 2 => esc_html( sprintf( '%s deleted.', $this->taxo->tax_singular ) ), + 3 => esc_html( sprintf( '%s updated.', $this->taxo->tax_singular ) ), + 4 => esc_html( sprintf( '%s not added.', $this->taxo->tax_singular ) ), + 5 => esc_html( sprintf( '%s not updated.', $this->taxo->tax_singular ) ), + 6 => esc_html( sprintf( '%s deleted.', $this->taxo->tax_plural ) ), + ]; + + return $messages; + } + + /** + * A non-localised version of _n() + * + * @param string $single The text that will be used if $number is 1. + * @param string $plural The text that will be used if $number is not 1. + * @param int $number The number to compare against to use either $single or $plural. + * @return string Either $single or $plural text. + */ + public static function n( string $single, string $plural, int $number ): string { + return ( 1 === intval( $number ) ) ? $single : $plural; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/TaxonomyRewriteTesting.php b/vendor/johnbillion/extended-cpts/src/TaxonomyRewriteTesting.php new file mode 100644 index 0000000..c9d7e4d --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/TaxonomyRewriteTesting.php @@ -0,0 +1,40 @@ +taxo = $taxo; + } + + /** + * @return array> + */ + public function get_tests(): array { + global $wp_rewrite; + + if ( ! $wp_rewrite->using_permalinks() ) { + return []; + } + + if ( ! isset( $wp_rewrite->extra_permastructs[ $this->taxo->taxonomy ] ) ) { + return []; + } + + $struct = $wp_rewrite->extra_permastructs[ $this->taxo->taxonomy ]; + /** @var WP_Taxonomy */ + $tax = get_taxonomy( $this->taxo->taxonomy ); + $name = sprintf( '%s (%s)', $tax->labels->name, $this->taxo->taxonomy ); + + return [ + $name => $this->get_rewrites( $struct, [] ), + ]; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/Walker/Checkboxes.php b/vendor/johnbillion/extended-cpts/src/Walker/Checkboxes.php new file mode 100644 index 0000000..217017f --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/Walker/Checkboxes.php @@ -0,0 +1,119 @@ + elements properly. + */ +class Checkboxes extends \Walker { + + /** + * @var string + */ + public $tree_type = 'category'; + + /** + * @var array + */ + public $db_fields = [ + 'parent' => 'parent', + 'id' => 'term_id', + ]; + + /** + * @var string + */ + public $field = null; + + /** + * Class constructor. + * + * @param array $args Optional arguments. + */ + public function __construct( $args = null ) { + if ( $args && isset( $args['field'] ) ) { + $this->field = $args['field']; + } + } + + /** + * Starts the list before the elements are added. + * + * @param string $output Passed by reference. Used to append additional content. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @return void + */ + public function start_lvl( &$output, $depth = 0, $args = [] ) { + $indent = str_repeat( "\t", $depth ); + $output .= "$indent
    \n"; + } + + /** + * Ends the list of after the elements are added. + * + * @param string $output Passed by reference. Used to append additional content. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @return void + */ + public function end_lvl( &$output, $depth = 0, $args = [] ) { + $indent = str_repeat( "\t", $depth ); + $output .= "$indent
\n"; + } + + /** + * Start the element output. + * + * @param string $output Passed by reference. Used to append additional content. + * @param WP_Term $object Term data object. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @param int $current_object_id Current object ID. + * @return void + */ + public function start_el( &$output, $object, $depth = 0, $args = [], $current_object_id = 0 ) { + $tax = get_taxonomy( $args['taxonomy'] ); + + if ( ! $tax ) { + return; + } + + if ( $this->field ) { + $value = $object->{$this->field}; + } else { + $value = $tax->hierarchical ? $object->term_id : $object->name; + } + + if ( empty( $object->term_id ) && ! $tax->hierarchical ) { + $value = ''; + } + + $output .= "\n
  • term_id}'>" . + ''; + } + + /** + * Ends the element output, if needed. + * + * @param string $output Passed by reference. Used to append additional content. + * @param WP_Term $object Term data object. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @return void + */ + public function end_el( &$output, $object, $depth = 0, $args = [] ) { + $output .= "
  • \n"; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/Walker/Dropdown.php b/vendor/johnbillion/extended-cpts/src/Walker/Dropdown.php new file mode 100644 index 0000000..1a35f2c --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/Walker/Dropdown.php @@ -0,0 +1,89 @@ + + */ + public $db_fields = [ + 'parent' => 'parent', + 'id' => 'term_id', + ]; + + /** + * @var string + */ + public $field = null; + + /** + * Class constructor. + * + * @param array $args Optional arguments. + */ + public function __construct( $args = null ) { + if ( $args && isset( $args['field'] ) ) { + $this->field = $args['field']; + } + } + + /** + * Start the element output. + * + * @param string $output Passed by reference. Used to append additional content. + * @param WP_Term $object Term data object. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @param int $current_object_id Current object ID. + * @return void + */ + public function start_el( &$output, $object, $depth = 0, $args = [], $current_object_id = 0 ) { + $pad = str_repeat( ' ', $depth * 3 ); + $tax = get_taxonomy( $args['taxonomy'] ); + + if ( ! $tax ) { + return; + } + + if ( $this->field ) { + $value = $object->{$this->field}; + } else { + $value = $tax->hierarchical ? $object->term_id : $object->name; + } + + if ( empty( $object->term_id ) && ! $tax->hierarchical ) { + $value = ''; + } + + $cat_name = apply_filters( 'list_cats', $object->name, $object ); + $output .= "\t\n"; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/Walker/Radios.php b/vendor/johnbillion/extended-cpts/src/Walker/Radios.php new file mode 100644 index 0000000..1e5091d --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/Walker/Radios.php @@ -0,0 +1,119 @@ + + */ + public $db_fields = [ + 'parent' => 'parent', + 'id' => 'term_id', + ]; + + /** + * @var string + */ + public $field = null; + + /** + * Class constructor. + * + * @param array $args Optional arguments. + */ + public function __construct( $args = null ) { + if ( $args && isset( $args['field'] ) ) { + $this->field = $args['field']; + } + } + + /** + * Starts the list before the elements are added. + * + * @param string $output Passed by reference. Used to append additional content. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @return void + */ + public function start_lvl( &$output, $depth = 0, $args = [] ) { + $indent = str_repeat( "\t", $depth ); + $output .= "{$indent}
      \n"; + } + + /** + * Ends the list of after the elements are added. + * + * @param string $output Passed by reference. Used to append additional content. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @return void + */ + public function end_lvl( &$output, $depth = 0, $args = [] ) { + $indent = str_repeat( "\t", $depth ); + $output .= "{$indent}
    \n"; + } + + /** + * Start the element output. + * + * @param string $output Passed by reference. Used to append additional content. + * @param WP_Term $object Term data object. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @param int $current_object_id Current object ID. + * @return void + */ + public function start_el( &$output, $object, $depth = 0, $args = [], $current_object_id = 0 ) { + $tax = get_taxonomy( $args['taxonomy'] ); + + if ( ! $tax ) { + return; + } + + if ( $this->field ) { + $value = $object->{$this->field}; + } else { + $value = $tax->hierarchical ? $object->term_id : $object->name; + } + + if ( empty( $object->term_id ) && ! $tax->hierarchical ) { + $value = ''; + } + + $output .= "\n
  • term_id}'>" . + ''; + } + + /** + * Ends the element output, if needed. + * + * @param string $output Passed by reference. Used to append additional content. + * @param WP_Term $object Term data object. + * @param int $depth Depth of term in reference to parents. + * @param array $args Optional arguments. + * @return void + */ + public function end_el( &$output, $object, $depth = 0, $args = [] ) { + $output .= "
  • \n"; + } + +} diff --git a/vendor/johnbillion/extended-cpts/src/dashicons-codepoints.json b/vendor/johnbillion/extended-cpts/src/dashicons-codepoints.json new file mode 100644 index 0000000..e72232a --- /dev/null +++ b/vendor/johnbillion/extended-cpts/src/dashicons-codepoints.json @@ -0,0 +1,345 @@ +{ + "menu": 62259, + "admin-site": 62233, + "dashboard": 61990, + "admin-media": 61700, + "admin-page": 61701, + "admin-comments": 61697, + "admin-appearance": 61696, + "admin-plugins": 61702, + "admin-users": 61712, + "admin-tools": 61703, + "admin-settings": 61704, + "admin-network": 61714, + "admin-generic": 61713, + "admin-home": 61698, + "admin-collapse": 61768, + "filter": 62774, + "admin-customizer": 62784, + "admin-multisite": 62785, + "admin-links": 61699, + "admin-post": 61705, + "format-image": 61736, + "format-gallery": 61793, + "format-audio": 61735, + "format-video": 61734, + "format-chat": 61733, + "format-status": 61744, + "format-aside": 61731, + "format-quote": 61730, + "welcome-write-blog": 61721, + "welcome-add-page": 61747, + "welcome-view-site": 61717, + "welcome-widgets-menus": 61718, + "welcome-comments": 61719, + "welcome-learn-more": 61720, + "image-crop": 61797, + "image-rotate": 62769, + "image-rotate-left": 61798, + "image-rotate-right": 61799, + "image-flip-vertical": 61800, + "image-flip-horizontal": 61801, + "image-filter": 62771, + "undo": 61809, + "redo": 61810, + "editor-bold": 61952, + "editor-italic": 61953, + "editor-ul": 61955, + "editor-ol": 61956, + "editor-quote": 61957, + "editor-alignleft": 61958, + "editor-aligncenter": 61959, + "editor-alignright": 61960, + "editor-insertmore": 61961, + "editor-spellcheck": 61968, + "editor-expand": 61969, + "editor-contract": 62726, + "editor-kitchensink": 61970, + "editor-underline": 61971, + "editor-justify": 61972, + "editor-textcolor": 61973, + "editor-paste-word": 61974, + "editor-paste-text": 61975, + "editor-removeformatting": 61976, + "editor-video": 61977, + "editor-customchar": 61984, + "editor-outdent": 61985, + "editor-indent": 61986, + "editor-help": 61987, + "editor-strikethrough": 61988, + "editor-unlink": 61989, + "editor-rtl": 62240, + "editor-break": 62580, + "editor-code": 62581, + "editor-code-duplicate": 62612, + "editor-paragraph": 62582, + "editor-table": 62773, + "align-left": 61749, + "align-right": 61750, + "align-center": 61748, + "align-none": 61752, + "lock": 61792, + "lock-duplicate": 62229, + "unlock": 62760, + "calendar": 61765, + "calendar-alt": 62728, + "visibility": 61815, + "hidden": 62768, + "post-status": 61811, + "edit": 62564, + "edit-large": 62247, + "sticky": 62775, + "external": 62724, + "arrow-up": 61762, + "arrow-up-duplicate": 61763, + "arrow-down": 61760, + "arrow-left": 61761, + "arrow-right": 61753, + "arrow-up-alt": 62274, + "arrow-down-alt": 62278, + "arrow-left-alt": 62272, + "arrow-right-alt": 62276, + "arrow-up-alt2": 62275, + "arrow-down-alt2": 62279, + "arrow-left-alt2": 62273, + "arrow-right-alt2": 62277, + "leftright": 61993, + "sort": 61782, + "randomize": 62723, + "list-view": 61795, + "excerpt-view": 61796, + "grid-view": 62729, + "move": 62789, + "hammer": 62216, + "art": 62217, + "migrate": 62224, + "performance": 62225, + "universal-access": 62595, + "universal-access-alt": 62727, + "tickets": 62598, + "nametag": 62596, + "clipboard": 62593, + "heart": 62599, + "megaphone": 62600, + "schedule": 62601, + "wordpress": 61728, + "wordpress-alt": 62244, + "pressthis": 61783, + "update": 62563, + "screenoptions": 61824, + "cart": 61812, + "feedback": 61813, + "translation": 62246, + "tag": 62243, + "category": 62232, + "archive": 62592, + "tagcloud": 62585, + "text": 62584, + "media-archive": 62721, + "media-audio": 62720, + "media-code": 62617, + "media-default": 62616, + "media-document": 62615, + "media-interactive": 62614, + "media-spreadsheet": 62613, + "media-text": 62609, + "media-video": 62608, + "playlist-audio": 62610, + "playlist-video": 62611, + "controls-play": 62754, + "controls-pause": 62755, + "controls-forward": 62745, + "controls-skipforward": 62743, + "controls-back": 62744, + "controls-skipback": 62742, + "controls-repeat": 62741, + "controls-volumeon": 62753, + "controls-volumeoff": 62752, + "yes": 61767, + "no": 61784, + "no-alt": 62261, + "plus": 61746, + "plus-alt": 62722, + "plus-alt2": 62787, + "minus": 62560, + "dismiss": 61779, + "marker": 61785, + "star-filled": 61781, + "star-half": 62553, + "star-empty": 61780, + "flag": 61991, + "info": 62280, + "warning": 62772, + "share": 62007, + "share1": 62007, + "share-alt": 62016, + "share-alt2": 62018, + "twitter": 62209, + "rss": 62211, + "email": 62565, + "email-alt": 62566, + "facebook": 62212, + "facebook-alt": 62213, + "networking": 62245, + "googleplus": 62562, + "location": 62000, + "location-alt": 62001, + "camera": 62214, + "images-alt": 62002, + "images-alt2": 62003, + "video-alt": 62004, + "video-alt2": 62005, + "video-alt3": 62006, + "vault": 61816, + "shield": 62258, + "shield-alt": 62260, + "sos": 62568, + "search": 61817, + "slides": 61825, + "analytics": 61827, + "chart-pie": 61828, + "chart-bar": 61829, + "chart-line": 62008, + "chart-area": 62009, + "groups": 62215, + "businessman": 62264, + "id": 62262, + "id-alt": 62263, + "products": 62226, + "awards": 62227, + "forms": 62228, + "testimonial": 62579, + "portfolio": 62242, + "book": 62256, + "book-alt": 62257, + "download": 62230, + "upload": 62231, + "backup": 62241, + "clock": 62569, + "lightbulb": 62265, + "microphone": 62594, + "desktop": 62578, + "laptop": 62791, + "tablet": 62577, + "smartphone": 62576, + "phone": 62757, + "smiley": 62248, + "index-card": 62736, + "carrot": 62737, + "building": 62738, + "store": 62739, + "album": 62740, + "palmtree": 62759, + "tickets-alt": 62756, + "money": 62758, + "thumbs-up": 62761, + "thumbs-down": 62786, + "layout": 62776, + "paperclip": 62790, + "email-alt2": 62567, + "menu-alt": 61992, + "trash": 61826, + "heading": 61710, + "insert": 61711, + "align-full-width": 61716, + "button": 61722, + "align-wide": 61723, + "ellipsis": 61724, + "buddicons-activity": 62546, + "buddicons-buddypress-logo": 62536, + "buddicons-community": 62547, + "buddicons-forums": 62537, + "buddicons-friends": 62548, + "buddicons-groups": 62550, + "buddicons-pm": 62551, + "buddicons-replies": 62545, + "buddicons-topics": 62544, + "buddicons-tracking": 62549, + "admin-site-alt": 61725, + "admin-site-alt2": 61726, + "admin-site-alt3": 61727, + "rest-api": 61732, + "yes-alt": 61738, + "buddicons-bbpress-logo": 62583, + "tide": 61709, + "editor-ol-rtl": 61740, + "instagram": 61741, + "businessperson": 61742, + "businesswoman": 61743, + "color-picker": 61745, + "camera-alt": 61737, + "editor-ltr": 61708, + "cloud": 61814, + "twitter-alt": 62210, + "menu-alt2": 62249, + "menu-alt3": 62281, + "plugins-checked": 62597, + "text-page": 61729, + "update-alt": 61715, + "code-standards": 61754, + "align-pull-left": 61706, + "align-pull-right": 61707, + "block-default": 61739, + "cloud-saved": 61751, + "cloud-upload": 61755, + "columns": 61756, + "cover-image": 61757, + "embed-audio": 61758, + "embed-generic": 61759, + "embed-photo": 61764, + "embed-post": 61766, + "embed-video": 61769, + "exit": 61770, + "html": 61771, + "info-outline": 61772, + "insert-after": 61773, + "insert-before": 61774, + "remove": 61775, + "shortcode": 61776, + "table-col-after": 61777, + "table-col-before": 61778, + "table-col-delete": 61786, + "table-row-after": 61787, + "table-row-before": 61788, + "table-row-delete": 61789, + "saved": 61790, + "airplane": 61791, + "amazon": 61794, + "bank": 61802, + "beer": 61804, + "bell": 61805, + "calculator": 61806, + "coffee": 61807, + "database-add": 61808, + "database-export": 61818, + "database-import": 61819, + "database-remove": 61820, + "database-view": 61821, + "database": 61822, + "drumstick": 61823, + "edit-page": 61830, + "food": 61831, + "fullscreen-alt": 61832, + "fullscreen-exit-alt": 61833, + "games": 61834, + "google": 61835, + "hourglass": 61836, + "linkedin": 61837, + "money-alt": 61838, + "open-folder": 61839, + "pdf": 61840, + "pets": 61841, + "pinterest": 61842, + "printer": 61843, + "privacy": 61844, + "reddit": 61845, + "spotify": 61846, + "superhero-alt": 61847, + "superhero": 61848, + "twitch": 61849, + "whatsapp": 61850, + "youtube": 61851, + "car": 61803, + "podio": 61852, + "xing": 61853 +}