├── .distignore ├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── install-wp-tests.sh ├── composer.json ├── dfm-transients.php ├── includes ├── admin │ ├── class-dfm-transient-admin.php │ ├── class-dfm-transient-control-ui.php │ └── class-dfm-transient-meta-ui.php ├── class-dfm-async-handler.php ├── class-dfm-async-nonce.php ├── class-dfm-transient-hook.php ├── class-dfm-transient-scheduler.php ├── class-dfm-transient-utils.php ├── class-dfm-transients.php ├── cli.php └── template-tags.php ├── phpcs.xml.dist ├── phpunit.xml.dist └── tests ├── bootstrap.php ├── test-class-dfm-async-handler.php ├── test-class-dfm-async-nonce.php ├── test-class-dfm-transient-hook.php ├── test-class-dfm-transient-scheduler.php ├── test-class-dfm-transients.php └── test-template-tags.php /.distignore: -------------------------------------------------------------------------------- 1 | # A set of files you probably don't want in your WordPress.org distribution 2 | .distignore 3 | .editorconfig 4 | .git 5 | .gitignore 6 | .gitlab-ci.yml 7 | .travis.yml 8 | .DS_Store 9 | Thumbs.db 10 | behat.yml 11 | bin 12 | circle.yml 13 | composer.json 14 | composer.lock 15 | Gruntfile.js 16 | package.json 17 | phpunit.xml 18 | phpunit.xml.dist 19 | multisite.xml 20 | multisite.xml.dist 21 | phpcs.xml 22 | phpcs.xml.dist 23 | README.md 24 | wp-cli.local.yml 25 | tests 26 | vendor 27 | node_modules 28 | *.sql 29 | *.tar.gz 30 | *.zip 31 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | [{.jshintrc,*.json,*.yml}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [{*.txt,wp-config-sample.php}] 22 | end_of_line = crlf 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | *.swp 4 | .htaccess 5 | node_modules 6 | .sass-cache 7 | .buildpath 8 | .project 9 | .settings 10 | .idea/* 11 | *.sql 12 | *.tar.gz 13 | *.zip 14 | wp-cli.local.yml 15 | Thumbs.db 16 | coverage/ 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | notifications: 6 | email: 7 | on_success: never 8 | on_failure: change 9 | 10 | branches: 11 | only: 12 | - master 13 | 14 | cache: 15 | directories: 16 | - vendor 17 | - $HOME/.composer/cache 18 | 19 | matrix: 20 | include: 21 | - php: 7.1 22 | env: WP_VERSION=latest 23 | - php: 7.0 24 | env: WP_VERSION=latest 25 | - php: 5.6 26 | env: WP_VERSION=4.7 27 | - php: 5.6 28 | env: WP_VERSION=latest 29 | - php: 5.6 30 | env: WP_VERSION=trunk 31 | # - php: 5.6 32 | # env: WP_TRAVISCI=phpcs 33 | 34 | before_script: 35 | - export PATH="$HOME/.composer/vendor/bin:$PATH" 36 | - | 37 | if [[ ! -z "$WP_VERSION" ]] ; then 38 | bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION 39 | composer global require "phpunit/phpunit=5.7.*" 40 | fi 41 | - | 42 | if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then 43 | composer global require wp-coding-standards/wpcs 44 | phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs 45 | fi 46 | 47 | script: 48 | - | 49 | if [[ ! -z "$WP_VERSION" ]] ; then 50 | phpunit 51 | WP_MULTISITE=1 phpunit 52 | fi 53 | - | 54 | if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then 55 | phpcs 56 | fi 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DFM-Transients 2 | Transient management utilities for WP CMS 3 | 4 | This WordPress plugin provides utilities to expand upon the built in WordPress Transients API. The focus of this library is to make it easier to get & set transients, as well as introduce asynchronous update abilities for regenerating data to be stored in transients. 5 | 6 | ## Sample Code for registering a transient 7 | Below is a sample of what it would look like to register an actual transient. The pattern is similar to what you would do for registering a post type, or a taxonomy. The first parameter passed to `dfm_register_transient()` is the name of the transient, and the second is the array of arguments for registering the transient. For a full list of arguments you can pass to this function, and what they do, view the list below. 8 | ```php 9 | function dfm_register_sample_transient() { 10 | 11 | $transient_args = array( 12 | 'cache_type' => 'transient', 13 | 'callback' => 'dfm_transient_callback', 14 | 'expiration' => DAY_IN_SECONDS, 15 | 'soft_expiration' => true, 16 | 'update_hooks' => array( 17 | 'updated_post_meta' => 'dfm_transient_meta_update_cb', 18 | ), 19 | ); 20 | 21 | dfm_register_transient( 'sample_transient', $transient_args ); 22 | 23 | } 24 | 25 | add_action( 'after_setup_theme', 'dfm_register_sample_transient' ); 26 | ``` 27 | 28 | ## Sample transient callback 29 | This callback function would pair with the transient registered above. 30 | ```php 31 | dfm_transient_callback( $modifier ) { 32 | $args = array( 33 | 'post_type' => 'post', 34 | 'post_status' => 'publish', 35 | 'posts_per_page' => 5, 36 | 'tax_query' => array( 37 | array( 38 | 'taxonomy' => 'category', 39 | 'terms' => $modifier, 40 | ), 41 | ), 42 | ); 43 | $posts = new WP_Query( $args ); 44 | return $posts; 45 | } 46 | ``` 47 | ## Sample update callback 48 | This function would pair with the transient registered above. It decides if we should actually run the callback to regenerate the transient data on this hook. 49 | ```php 50 | dfm_transient_meta_update_cb( $args ) { 51 | // Matches $meta_key value (3rd arg passed to hook) 52 | if ( 'my_meta_key' === $args[2] ) { 53 | // Returns post ID 54 | return $args[1] 55 | } else { 56 | // If this callback returns false, we will not regenerate the transient data. 57 | return false; 58 | } 59 | ``` 60 | ## Retrieve the transient data 61 | You can use the `dfm_get_transient()` function to retrieve the data for a transient. The first parameter passed to this function is the name of the transient you are trying to retrieve (should be the same name that you registered the transient with). The second parameter is the "modifier" for the transient. You can read more about modifiers below. The third parameter is the object_id (if you are using post_meta, term_meta, or user_meta as the cache type). 62 | ```php 63 | $result = dfm_get_transient( 'sample_transient' ); 64 | ``` 65 | ```php 66 | $post_transient = dfm_get_transient( 'sample_transient', '', $post_id ); 67 | ``` 68 | 69 | ## Arguments for registering a transient 70 | 1. **key** (string) - The unique name of the transient that will be used to set the storage key. By default, this will use the transient name (passed as the first parameter to `dfm_register_transient` . If this needs to be overridden for some reason, use this argument. *Default: transient name* 71 | 2. **hash_key** (bool) - Set to true if you want to MD5 hash your storage key. If you are using *_meta for the storage engine, the prefix will not be hashed. *Default: False* 72 | 3. **cache_type** (string) - The cache engine you would like to use for this transient. Defines where in the database where the actual transient should be stored. *Default: transient*. Options are: 73 | - transient - Stored as a normal transient in the options table 74 | - post_meta - Stored as post meta 75 | - term_meta - Stored as term meta 76 | - user_meta - stored as user meta 77 | 4. **callback** (string|array|callable) - The callback for the transient, this is the heart and soul of the framework. Here you can point to a callback function to be run to re-populate the data stored in the transient. The function can return any data that can be stored in a native transient or option. The function is passed the `$modifier` for the transient. For a transient using a `*_meta` storage engine, the `$modifier` will be the object ID the meta is attached to. Otherwise it will be any piece of data that makes a global transient unique (more info on this below). Good to note is that this callback argument **must** be used. You will not be able to register a transient without it. 78 | 5. **async_updates** (bool) - Set to true for your transients to update asynchronously. This only applies to updates that come from actions registered through the `update_hooks` argument. For async updates on transient expiration see `soft_expiration` *Default: False* 79 | 6. **update_hooks** (array|bool|string) - Defines which hooks we should hook into to regenerate the transient data. You can pass a single hook to this argument as a string if you just want to regenerate the data whenever a certain hook fires. You can also pass an array of hooks if you want the transient data to be regenerated for multiple hooks. When passing as an array, you can also pass a callback that decides if the data regeneration should actually run on this hook. This is helpful when using generic hooks like `updated_post_meta`. For example, the `updated_post_meta` hook runs whenever any piece of post meta is updated, but you may only want to update your transient data when a certain piece of post meta is updated. The callback is passed all of the arguments from the hook as an array using `func_get_args()`, so you can compare against the arguments. The callback should return `false` if the regeneration should not run, and should return the `$modifier` to be passed to the regeneration callback if it should run. You can also return an array of modifiers from your callback, so you can regenerate data for multiple transients within the same transient group. Each of the hooks can have their own callback, if you have multiple. It will look something like this: `array( 'my_hook' => 'callback' );`. Essentially in the array of update hooks, the key is the name of the hook to fire on, and the value is the name of your callback function. *Default: False* 80 | 7. **expiration** (bool|int) - When the transient should expire. This works exactly how it works with normal transients, so you can pass something like `HOUR_IN_SECONDS` here. *Default: False* 81 | 8. **soft_expiration** (bool) - Whether or not the data should soft expire or not. If this is set to true, it will check to see if the data has expired when retrieving it. If it is expired, it will spawn a background process to update the transient data. *Default: False* 82 | 83 | ## Transient Modifier 84 | The Transient modifier (second parameter passed to the `dfm_get_transient` function) is used to store variations of the same type of transient. It will append the $modifier to the end of the transient key. This way you could store and retrieve different variations of the same transient that are mostly the same without registering a whole new transient. You can use the modifier to change the data saved to the transient by using it to alter your logic in your callback (the modifier is passed as the first argument to your callback function). 85 | 86 | ## Debugging 87 | To help with debugging, you can set a constant in your codebase called `DFM_TRANSIENTS_HOT_RELOAD` and set it to `true` to enable "hot reload" mode. This will essentially make it so that transient data will be regenerated every time it is called. This is handy if you are working on adding a transient, and want it to keep regenerating while you are working on it. This saves the need from manually deleting it from your database, or setting an extremely short timeout. **NOTE:** This constant should only ever be used on a development environment. Using this on production could cause serious performance issues depending on the data you are storing in your transients. 88 | 89 | ## Retries 90 | Since version 1.1.0 there is a retry facilitation system for DFM Transients. This is helpful if you are storing data from an external API, and want to serve stale data if the API is down. To use this feature, all you have to do is return `false` or a `wp_error` object in your transient callback if your remote request failed. This will then store the stale expired data back into the transient, and will use an expiration timeout that increases exponentially every time it fails to fetch the data. Essentially it stores a `failed` value in the cache for each transient, and adds one to the value every time the retry method runs. It then mulitplies this number by its self to figure out how many minutes it should set the expiration to. For example, if the fetch has failed 5 times, it will set the timeout to 25 minutes, and will retry again after that. 91 | 92 | ## CLI Commands 93 | DFM Transients comes with a full list of CLI commands to control transients from the command line. Below are a few sample commands to get you started. 94 | 95 | List all registered transients: 96 | ```bash 97 | $ wp dfm-transients list 98 | 99 | +----------------------------+----------+------------+---------------+------------+-----------------+ 100 | | key | hash_key | cache_type | async_updates | expiration | soft_expiration | 101 | +----------------------------+----------+------------+---------------+------------+-----------------+ 102 | | dfm_instagram_api_feed | | transient | | 3600 | 1 | 103 | | dfm_current_standout_count | | transient | | 3600 | 1 | 104 | | author_featured_articles | | post_meta | 1 | | | 105 | | nav_menu | | transient | 1 | | | 106 | +----------------------------+----------+------------+---------------+------------+-----------------+ 107 | ``` 108 | You can also get a list of information on just a few registered transients, and return only the info you need with the `--fields` flag 109 | ```bash 110 | $ wp dfm-transients list dfm_current_standout_count author_featured_articles --fields=key,cache_type,async_updates 111 | 112 | +----------------------------+------------+---------------+ 113 | | key | cache_type | async_updates | 114 | +----------------------------+------------+---------------+ 115 | | dfm_current_standout_count | transient | | 116 | | author_featured_articles | post_meta | 1 | 117 | +----------------------------+------------+---------------+ 118 | ``` 119 | If you want a list of registered transients that all use async_updated you can run something like the following: 120 | ```bash 121 | $wp dfm-transients list --async_updates=1 --fields=key,cache_type,async_updates 122 | 123 | +--------------------------+------------+---------------+ 124 | | key | cache_type | async_updates | 125 | +--------------------------+------------+---------------+ 126 | | author_featured_articles | post_meta | 1 | 127 | | nav_menu | transient | 1 | 128 | | author_list_query | transient | 1 | 129 | | term_posts | term_meta | 1 | 130 | +--------------------------+------------+---------------+ 131 | ``` 132 | You can also retrieve the data from your transients from the command line: 133 | ```bash 134 | $wp dfm-transients get term_posts all 135 | 136 | +----------+------------------------------------------------------------------------------------------------------------------+ 137 | | modifier | data | 138 | +----------+------------------------------------------------------------------------------------------------------------------+ 139 | | 7681 | [2187069,2189051,2188653,2188525,2188689,2188302,2188435,2188561,2188519,2188486,2188180,2188052,2188378,2187293 | 140 | | | ,2187831,2185712,2186525,2186643,2186373,2186221] | 141 | | 4687 | [2188121,2187831,2187084,2185789,2185208,2185126,2185123,2185003,2183357,2183326,2183276,2183089,2183081,2183060 | 142 | | | ,2182991,2181531,2180932,2180486,2177749,2179168] | 143 | | 1797 | [2186592,2177875,2170239,2162981,2155404,2148740] | 144 | | 8732 | [2188653,2185461,2183384,2182193,2178326,2175819,2174603,2170396,2168493,2167170,2163516,2160893,2158586,2156073 | 145 | | | ,2154094,2151996,2149292,2147779,2147211,2147178] | 146 | +----------+------------------------------------------------------------------------------------------------------------------+ 147 | ``` 148 | You can also retrieve only the modifiers for the transients, which becomes useful with the delete and set commands 149 | ```bash 150 | $wp dfm-transients get term_posts all --fields=modifier --format=ids 151 | 152 | 97 7681 4687 1797 8732 8624 98 9372 7682 48 94 75 66 30 15 7629 40 53 59 36 153 | ``` 154 | For modifying the data stored in a transient, you can use the `set` command like so: 155 | ```bash 156 | $wp dfm-transients set my_transient --data="test" 157 | Successfully updated the my_transient transient 158 | ``` 159 | You can also combine this with the `get` command to update multiple transients within a group at once 160 | ```bash 161 | $wp dfm-transients set term_posts $(wp dfm-transients get term_posts --fields=modifier --format=ids) --data="test" 162 | Updating Transients 100% [=============================================] 0:00 / 0:00 163 | Success: Successfully updated 20 transients 164 | ``` 165 | Similar to the `set` command, you can use the `delete` command to delete the record for the transient. This is useful for when you want to force the system to regenerate the data stored in a particular transient. 166 | ```bash 167 | $wp dfm-transients delete my_transient 168 | Success: Successfully deleted transient: my_transient 169 | ``` 170 | You can also combine this command with the `get` command to delete multiple transients at the same time 171 | ```bash 172 | $wp dfm-transients delete term_posts $(wp dfm-transients get term_posts --fields=modifier --format=ids) 173 | Deleting transients 100% [=============================================] 0:00 / 0:00 174 | Success: Successfully deleted 20 transients 175 | ``` 176 | 177 | ## Contributing 178 | To contribute to this repo, please fork it and submit a pull request. If there is a larger feature you would like to see, or something you would like to discuss, please open an issue. 179 | ## Copyright 180 | © Media News Group 2016 181 | ## Attribution 182 | Props to the following projects for giving me ideas / code. 183 | - https://github.com/techcrunch/wp-async-task 184 | - https://github.com/markjaquith/WP-TLC-Transients 185 | - https://github.com/pippinsplugins/Transients-Manager 186 | 187 | ## License 188 | This library is licensed under the [MIT](http://opensource.org/licenses/MIT) license. See LICENSE.md for more details. 189 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} 16 | WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} 17 | 18 | download() { 19 | if [ `which curl` ]; then 20 | curl -s "$1" > "$2"; 21 | elif [ `which wget` ]; then 22 | wget -nv -O "$2" "$1" 23 | fi 24 | } 25 | 26 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then 27 | WP_TESTS_TAG="tags/$WP_VERSION" 28 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 29 | WP_TESTS_TAG="trunk" 30 | else 31 | # http serves a single offer, whereas https serves multiple. we only want one 32 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 33 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 34 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 35 | if [[ -z "$LATEST_VERSION" ]]; then 36 | echo "Latest WordPress version could not be found" 37 | exit 1 38 | fi 39 | WP_TESTS_TAG="tags/$LATEST_VERSION" 40 | fi 41 | 42 | set -ex 43 | 44 | install_wp() { 45 | 46 | if [ -d $WP_CORE_DIR ]; then 47 | return; 48 | fi 49 | 50 | mkdir -p $WP_CORE_DIR 51 | 52 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 53 | mkdir -p /tmp/wordpress-nightly 54 | download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip 55 | unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ 56 | mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR 57 | else 58 | if [ $WP_VERSION == 'latest' ]; then 59 | local ARCHIVE_NAME='latest' 60 | else 61 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 62 | fi 63 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz 64 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR 65 | fi 66 | 67 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 68 | } 69 | 70 | install_test_suite() { 71 | # portable in-place argument for both GNU sed and Mac OSX sed 72 | if [[ $(uname -s) == 'Darwin' ]]; then 73 | local ioption='-i .bak' 74 | else 75 | local ioption='-i' 76 | fi 77 | 78 | # set up testing suite if it doesn't yet exist 79 | if [ ! -d $WP_TESTS_DIR ]; then 80 | # set up testing suite 81 | mkdir -p $WP_TESTS_DIR 82 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 83 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 84 | fi 85 | 86 | if [ ! -f wp-tests-config.php ]; then 87 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 88 | # remove all forward slashes in the end 89 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 90 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 91 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 92 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 93 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 94 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 95 | fi 96 | 97 | } 98 | 99 | install_db() { 100 | 101 | if [ ${SKIP_DB_CREATE} = "true" ]; then 102 | return 0 103 | fi 104 | 105 | # parse DB_HOST for port or socket references 106 | local PARTS=(${DB_HOST//\:/ }) 107 | local DB_HOSTNAME=${PARTS[0]}; 108 | local DB_SOCK_OR_PORT=${PARTS[1]}; 109 | local EXTRA="" 110 | 111 | if ! [ -z $DB_HOSTNAME ] ; then 112 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 113 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 114 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 115 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 116 | elif ! [ -z $DB_HOSTNAME ] ; then 117 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 118 | fi 119 | fi 120 | 121 | # create database 122 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 123 | } 124 | 125 | install_wp 126 | install_test_suite 127 | install_db 128 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dfmedia/DFM-Transients", 3 | "type": "wordpress-plugin", 4 | "description": "Get better control over your transients", 5 | "homepage": "https://github.com/dfmedia/DFM-Transients", 6 | "license": "GPLv3", 7 | "require-dev": { 8 | "phpunit/phpunit": "6.1.*" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /dfm-transients.php: -------------------------------------------------------------------------------- 1 | load_dependencies(); 41 | add_action( 'init', array( $this, 'setup' ) ); 42 | add_action( 'admin_menu', array( $this, 'add_menu' ) ); 43 | add_filter( 'set-screen-option', array( $this, 'transient_set_option' ), 20, 3 ); 44 | } 45 | 46 | /** 47 | * Loads file dependencies 48 | * 49 | * @access private 50 | * @return void 51 | */ 52 | private function load_dependencies() { 53 | require_once( plugin_dir_path( __FILE__ ) . 'class-dfm-transient-meta-ui.php' ); 54 | require_once( plugin_dir_path( __FILE__ ) . 'class-dfm-transient-control-ui.php' ); 55 | } 56 | 57 | /** 58 | * Sets up some static variables at an execution point where the data can be retrieved & filtered 59 | * 60 | * @access public 61 | * @return void 62 | */ 63 | public function setup() { 64 | $this->cap = apply_filters( 'dfm_transient_capability', 'manage_options' ); 65 | $this->has_obj_cache = wp_using_ext_object_cache(); 66 | } 67 | 68 | /** 69 | * Adds the menu item for the transient control dashboard 70 | * 71 | * @access public 72 | * @return void 73 | */ 74 | public function add_menu() { 75 | 76 | $hook = add_submenu_page( 77 | 'tools.php', 78 | __( 'Transient Control', 'transient-control' ), 79 | __( 'Transient Control' ), 80 | $this->cap, 81 | 'transient-control', 82 | array( $this, 'admin_screen' ) 83 | ); 84 | 85 | add_action( 'load-' . $hook, array( $this, 'screen_options' ) ); 86 | 87 | } 88 | 89 | /** 90 | * Creates the UI for the transient control admin dashboard 91 | * 92 | * @access public 93 | * @return void 94 | */ 95 | public function admin_screen() { 96 | 97 | echo '

' . esc_html__( 'Transient Control', 'dfm-transients' ) . '

'; 98 | echo '
'; 99 | echo '
'; 100 | $this->toggle_button( 'database', __( 'Database', 'dfm-transients' ) ); 101 | $this->toggle_button( 'cache', __( 'Cache', 'dfm-transients' ) ); 102 | echo '
'; 103 | $this->list_obj->prepare_items(); 104 | $this->list_obj->display(); 105 | echo '
'; 106 | echo '
'; 107 | 108 | } 109 | 110 | /** 111 | * Adds screen options for the transient UI dashboard 112 | * 113 | * @access public 114 | * @return void 115 | */ 116 | public function screen_options() { 117 | 118 | $args = array( 119 | 'label' => 'Transients Per Page', 120 | 'default' => 10, 121 | 'option' => 'transients_per_page', 122 | ); 123 | 124 | add_screen_option( 'per_page', $args ); 125 | 126 | // Class must be instantiated here. 127 | $this->list_obj = new DFM_Transient_Control_UI(); 128 | 129 | } 130 | 131 | /** 132 | * Handles saving of transients per page option 133 | * 134 | * @param bool|int $status screen option value. Default false to skip. 135 | * @param string $option The option name. 136 | * @param $value The number of rows to use. 137 | * 138 | * @return mixed 139 | * @access public 140 | */ 141 | public function transient_set_option( $status, $option, $value ) { 142 | if ( 'transients_per_page' === $option ) { 143 | return $value; 144 | } 145 | return $status; 146 | } 147 | 148 | /** 149 | * Generates the HTML for the transient type toggle buttons 150 | * 151 | * @param string $type 152 | * @param string $text 153 | * @return void 154 | * @access private 155 | */ 156 | private function toggle_button( $type, $text ) { 157 | 158 | $current_type = ( isset( $_GET['transient_type'] ) ) ? $_GET['transient_type'] : 'database'; 159 | $classes = array( 'button' ); 160 | 161 | if ( $current_type === $type ) { 162 | $classes[] = 'active'; 163 | $classes[] = 'button-primary'; 164 | } 165 | 166 | $url = add_query_arg( array( 'page' => 'transient-control', 'transient_type' => $type ), admin_url( 'tools.php' ) ); 167 | 168 | echo '' . esc_html( $text ) . ''; 169 | 170 | } 171 | } 172 | 173 | } 174 | 175 | new DFM_Transient_Admin(); 176 | -------------------------------------------------------------------------------- /includes/admin/class-dfm-transient-control-ui.php: -------------------------------------------------------------------------------- 1 | __( 'Transient', 'dfm-transients' ), 32 | 'plural' => __( 'Transients', 'dfm-transients' ), 33 | 'ajax' => false, 34 | ) ); 35 | 36 | $this->get_cache_type(); 37 | 38 | } 39 | 40 | /** 41 | * Figure out which transient cache type view we are on 42 | * 43 | * @access private 44 | * @return void 45 | */ 46 | private function get_cache_type() { 47 | 48 | $type = ( isset( $_GET['transient_type'] ) ) ? $_GET['transient_type'] : 'database'; 49 | $this->cache_type = $type; 50 | 51 | } 52 | 53 | /** 54 | * Extension of the prepare_items method in the parent class 55 | * 56 | * @access public 57 | * @return void 58 | */ 59 | public function prepare_items() { 60 | 61 | $this->_column_headers = $this->get_column_info(); 62 | 63 | $per_page = $this->get_items_per_page( 'transients_per_page', 10 ); 64 | $current_page = $this->get_pagenum(); 65 | $total_items = $this->record_count(); 66 | 67 | $this->set_pagination_args( array( 68 | 'total_items' => $total_items, 69 | 'per_page' => $per_page, 70 | ) ); 71 | 72 | $this->items = $this->get_transients( $per_page, $current_page ); 73 | 74 | } 75 | 76 | /** 77 | * Figures out which get method to use based on our transient cache type 78 | * 79 | * @param int $per_page 80 | * @param int $current_page 81 | * @access public 82 | * @return array|null|object 83 | */ 84 | public function get_transients( $per_page = 10, $current_page = 0 ) { 85 | 86 | if ( 'cache' === $this->cache_type ) { 87 | $transients = $this->get_cache_transients( $per_page, $current_page ); 88 | } else { 89 | $transients = $this->get_db_transients( $per_page, $current_page ); 90 | } 91 | return $transients; 92 | 93 | } 94 | 95 | /** 96 | * Retrieves transients from the database 97 | * 98 | * @param int $per_page 99 | * @param int $current_page 100 | * @access private 101 | * @return array|null|object 102 | */ 103 | private function get_db_transients( $per_page, $current_page ) { 104 | 105 | global $wpdb; 106 | 107 | $per_page = absint( $per_page ); 108 | $current_page = absint( $current_page ); 109 | 110 | $results = $wpdb->get_results( $wpdb->prepare( 111 | " 112 | SELECT * 113 | FROM $wpdb->options 114 | WHERE option_name 115 | LIKE %s 116 | AND option_name 117 | NOT LIKE %s 118 | ORDER BY option_id DESC 119 | LIMIT %d, %d; 120 | ", 121 | '%\_transient\_%', 122 | '%\_transient\_timeout%', 123 | $current_page, 124 | $per_page 125 | ), 'ARRAY_A' ); 126 | 127 | $this->raw_transient_results = $results; 128 | return $results; 129 | 130 | } 131 | 132 | /** 133 | * Retrieves transients from the object cache 134 | * 135 | * @param int $per_page 136 | * @param int $current_page 137 | * @access private 138 | * @return array 139 | */ 140 | private function get_cache_transients( $per_page, $current_page ) { 141 | 142 | if ( empty( $this->raw_transient_results ) ) { 143 | global $wp_object_cache; 144 | $raw_keys = $wp_object_cache->cache; 145 | $transients = array(); 146 | if ( ! empty( $raw_keys ) && is_array( $raw_keys ) ) { 147 | foreach ( $raw_keys as $transient_name => $value ) { 148 | if ( 'wp_:transient' === substr( $transient_name, 0, 13 ) ) { 149 | $transients[] = array( 'option_name' => $transient_name, 'option_value' => $value ); 150 | } 151 | } 152 | } 153 | $this->raw_transient_results = $transients; 154 | } else { 155 | $transients = $this->raw_transient_results; 156 | } 157 | 158 | if ( 0 !== $per_page && 0 !== $current_page ) { 159 | $transients = array_slice( $transients, ( ( $per_page * $current_page) - $per_page ), $per_page ); 160 | } 161 | 162 | return $transients; 163 | 164 | } 165 | 166 | /** 167 | * Calculates the total amount of records we have. This is needed for pagination to work 168 | * 169 | * @access public 170 | * @return int 171 | */ 172 | public function record_count() { 173 | 174 | if ( 'cache' === $this->cache_type ) { 175 | if ( empty( $this->raw_transient_results ) ) { 176 | $transients = $this->get_cache_transients( 0, 0 ); 177 | } else { 178 | $transients = $this->raw_transient_results; 179 | } 180 | $count = count( $transients ); 181 | } else { 182 | global $wpdb; 183 | $count = $wpdb->get_var( 184 | " 185 | SELECT count(option_id) 186 | FROM $wpdb->options 187 | WHERE option_name 188 | LIKE '%\_transient\_%' 189 | AND option_name 190 | NOT LIKE '%\_transient\_timeout%' 191 | " 192 | ); 193 | } 194 | 195 | return (int) $count; 196 | 197 | } 198 | 199 | /** 200 | * What to display if no items are found 201 | * 202 | * @access public 203 | * @return void 204 | */ 205 | public function no_items() { 206 | esc_html_e( 'No transients found', 'dfm-transients' ); 207 | } 208 | 209 | /** 210 | * Defines the columns we want to display in the list table 211 | * 212 | * @access public 213 | * @return array $columns 214 | */ 215 | public function get_columns() { 216 | 217 | $columns = array( 218 | 'option_name' => __( 'Transient Name', 'dfm-transients' ), 219 | 'option_value' => __( 'Transient Value', 'dfm-transients' ), 220 | ); 221 | 222 | return $columns; 223 | 224 | } 225 | 226 | /** 227 | * Defines which columns in our list table should be sortable. 228 | * 229 | * @return array $sortable_columns 230 | * @access public 231 | */ 232 | public function get_sortable_columns() { 233 | 234 | $sortable_columns = array( 235 | 'option_name' => array( 'option_name', false ), 236 | ); 237 | 238 | return $sortable_columns; 239 | 240 | } 241 | 242 | /** 243 | * Handles the output for each of the list table columns 244 | * 245 | * @param array $item 246 | * @param string $column_name 247 | * @access public 248 | * @return string 249 | */ 250 | public function column_default( $item, $column_name ) { 251 | 252 | switch ( $column_name ) { 253 | case 'option_name': 254 | return '' . $item[ $column_name ] . ''; 255 | case 'option_value': 256 | // @todo: move inline styles to stylesheet once we have more than one admin style. 257 | return ''; 258 | default: 259 | return esc_html( $item[ $column_name ] ); 260 | } 261 | 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /includes/admin/class-dfm-transient-meta-ui.php: -------------------------------------------------------------------------------- 1 | prefix = apply_filters( 'dfm_transient_prefix', 'dfm_transient_' ); 52 | $this->cap = apply_filters( 'dfm_transient_capability', 'manage_options' ); 53 | $taxonomies = get_taxonomies( array( 'public' => true ) ); 54 | if ( ! empty( $taxonomies ) && is_array( $taxonomies ) ) { 55 | foreach ( $taxonomies as $taxonomy ) { 56 | add_action( 'edit_' . $taxonomy . '_form_fields', array( $this, 'render_transient_list' ), 999, 1 ); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * Sets the current screen context 63 | * 64 | * @access public 65 | * @return void 66 | */ 67 | public function current_context() { 68 | $current_screen = get_current_screen(); 69 | $this->context = $current_screen->base; 70 | } 71 | 72 | /** 73 | * Registers the metabox for post-like objects 74 | * 75 | * @access public 76 | * @return void 77 | */ 78 | public function meta_box() { 79 | if ( current_user_can( $this->cap ) ) { 80 | add_meta_box( 'dfm_transient_ui', 'Transients' , array( $this, 'render_transient_list' ), array( 'post', 'page' ) ); 81 | } 82 | } 83 | 84 | /** 85 | * Renders the metabox 86 | * 87 | * @param object $object The term or user object passed from the hook. 88 | */ 89 | public function render_transient_list( $object ) { 90 | 91 | if ( ! current_user_can( $this->cap ) ) { 92 | return; 93 | } 94 | 95 | $transients = $this->get_transients( $object ); 96 | 97 | if ( ! empty( $transients ) ) { 98 | 99 | echo ''; 100 | echo ''; 101 | echo ''; 102 | echo ''; 103 | echo ''; 104 | echo ''; 105 | foreach ( $transients as $transient_key => $transient_value ) { 106 | 107 | $transient_value = maybe_unserialize( $transient_value[0] ); 108 | 109 | if ( array_key_exists( 'data', $transient_value ) ) { 110 | $data = $transient_value['data']; 111 | $expiration = date( 'm-d-y H:i:s', $transient_value['expiration'] ); 112 | } else { 113 | $data = $transient_value; 114 | $expiration = ''; 115 | } 116 | 117 | $data = var_export( $data, true ); 118 | 119 | echo ''; 120 | echo ''; 121 | echo ''; 122 | echo ''; 123 | echo ''; 124 | 125 | } 126 | 127 | echo '
' . esc_html__( 'Transient Key', 'dfm-transients' ) . '' . esc_html__( 'Value', 'dfm-transients' ) . '' . esc_html__( 'Expiration', 'dfm-transients' ) . '
' . esc_html( $transient_key ) . '' . esc_html( $expiration ) . '
'; 128 | 129 | } 130 | } 131 | 132 | /** 133 | * Retrieves the list of transients stored in the objects metadata 134 | * 135 | * @param object $object 136 | * @access private 137 | * @return array 138 | */ 139 | private function get_transients( $object ) { 140 | 141 | $transients = array(); 142 | 143 | if ( 'post' === $this->context ) { 144 | $object_id = get_the_ID(); 145 | $type = 'post'; 146 | } elseif ( 'term' === $this->context ) { 147 | $object_id = $object->term_id; 148 | $type = 'term'; 149 | } elseif ( 'profile' === $this->context ) { 150 | $object_id = $object->ID; 151 | $type = 'user'; 152 | } 153 | 154 | if ( isset( $type, $object_id ) ) { 155 | $meta = get_metadata( $type, $object_id ); 156 | 157 | $prefix_str_length = strlen( $this->prefix ); 158 | 159 | foreach ( $meta as $meta_key => $value ) { 160 | if ( substr( $meta_key, 0, $prefix_str_length ) === $this->prefix ) { 161 | $transients[ $meta_key ] = $value; 162 | } 163 | } 164 | } 165 | 166 | return $transients; 167 | 168 | } 169 | 170 | } 171 | 172 | } 173 | 174 | new DFM_Transient_Meta_UI(); 175 | -------------------------------------------------------------------------------- /includes/class-dfm-async-handler.php: -------------------------------------------------------------------------------- 1 | transient_name = $transient; 47 | $this->modifier = $modifier; 48 | $this->object_id = $object_id; 49 | $this->lock_key = $lock_key; 50 | // Spawn the event on shutdown so we are less likely to run into timeouts, or block other processes 51 | add_action( 'shutdown', array( $this, 'spawn_event' ) ); 52 | 53 | } 54 | 55 | /** 56 | * Sends off a request to process and update the transient data asynchronously 57 | * 58 | * @return array|void|WP_Error 59 | * @access public 60 | */ 61 | public function spawn_event() { 62 | 63 | // Prevents infinite loops if we are debugging transients in the init hook, or another hook that would run 64 | // when handling the async post data 65 | $is_async_action = ( isset( $_POST['async_action'] ) ) ? true : false; 66 | 67 | if ( true === $is_async_action ) { 68 | return; 69 | } 70 | 71 | $nonce = new DFM_Async_Nonce( $this->transient_name ); 72 | $nonce = $nonce->create(); 73 | 74 | $request_args = array( 75 | 'timeout' => 0.01, 76 | 'blocking' => false, // don't wait for a response 77 | 'body' => array( 78 | 'transient_name' => $this->transient_name, 79 | 'modifier' => $this->modifier, 80 | 'object_id' => $this->object_id, 81 | 'action' => 'dfm_' . $this->transient_name, 82 | '_nonce' => $nonce, 83 | 'async_action' => true, 84 | 'lock_key' => $this->lock_key, 85 | ), 86 | ); 87 | 88 | $url = admin_url( 'admin-post.php' ); 89 | return wp_safe_remote_post( $url, $request_args ); 90 | 91 | } 92 | 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /includes/class-dfm-async-nonce.php: -------------------------------------------------------------------------------- 1 | transient = $transient; 37 | $this->action = 'dfm_' . $this->transient; 38 | } 39 | 40 | /** 41 | * Creates a nonce for the async action 42 | * 43 | * @return string $nonce 44 | * @access public 45 | */ 46 | public function create() { 47 | $i = wp_nonce_tick(); 48 | return substr( wp_hash( $i . $this->action . get_class( $this ), 'nonce' ), -12, 10 ); 49 | } 50 | 51 | /** 52 | * Verifies the nonce for the async action 53 | * 54 | * @param string $nonce Nonce to verify against 55 | * @return bool 56 | * @access public 57 | */ 58 | public function verify( $nonce ) { 59 | 60 | $i = wp_nonce_tick(); 61 | 62 | // Nonce generated 0-12 hours ago 63 | if ( substr( wp_hash( $i . $this->action . get_class( $this ), 'nonce' ), -12, 10 ) === $nonce ) { 64 | return true; 65 | } 66 | 67 | // Nonce generated 12-24 hours ago 68 | if ( substr( wp_hash( ( $i - 1 ) . $this->action . get_class( $this ), 'nonce' ), -12, 10 ) === $nonce ) { 69 | return true; 70 | } 71 | 72 | return false; 73 | 74 | } 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /includes/class-dfm-transient-hook.php: -------------------------------------------------------------------------------- 1 | transient_name = $transient_name; 64 | $this->transient_obj = $transient_obj; 65 | $this->hook = $hook; 66 | $this->callback = $callback; 67 | $this->async = $async_update; 68 | 69 | $this->add_hook( $hook ); 70 | 71 | } 72 | 73 | /** 74 | * Dynamically add the hook for regenerating the transient 75 | * 76 | * @param string $hook 77 | * @return void 78 | * @access private 79 | */ 80 | private function add_hook( $hook ) { 81 | // Pass an arbitrarily high arg count to avoid errors. 82 | add_action( $hook, array( $this, 'spawn' ), 10, 20 ); 83 | } 84 | 85 | /** 86 | * Run an update in real time for the transient. 87 | * 88 | * @param string $modifier Name of the modifier 89 | * @return void 90 | * @access private 91 | */ 92 | private function run_update( $modifier, $object_id = null ) { 93 | 94 | if ( $this->async ) { 95 | new DFM_Async_Handler( $this->transient_name, $modifier, $object_id ); 96 | return; 97 | } 98 | 99 | $transient_obj = new DFM_Transients( $this->transient_name, $modifier, $object_id ); 100 | 101 | // Bail if another process is already trying to update this transient. 102 | if ( $transient_obj->is_locked() && ! $transient_obj->owns_lock( '' ) ) { 103 | return; 104 | } 105 | 106 | if ( ! $transient_obj->is_locked() ) { 107 | $transient_obj->lock_update(); 108 | } 109 | 110 | $data = call_user_func( $transient_obj->transient_object->callback, $modifier, $object_id ); 111 | 112 | $transient_obj->set( $data ); 113 | 114 | $transient_obj->unlock_update(); 115 | 116 | } 117 | 118 | /** 119 | * Spawn a process to regenerate the transient data 120 | * 121 | * @return void 122 | * @access public 123 | */ 124 | public function spawn() { 125 | 126 | $modifiers = ''; 127 | 128 | if ( ! empty( $this->callback ) ) { 129 | 130 | // Grab the args from the hook we are using 131 | $hook_args = func_get_args(); 132 | 133 | // Call the callback for this hook, and pass the args to it. 134 | // This callback decides if we should actually run the process to update the transient data based on the 135 | // args passed to it. The callback should return false if we don't want to run the action, and should 136 | // return the transient modifier if we do want to run it. You can also optionally return an array of 137 | // modifiers if you want to update multiple transients at once. 138 | $modifiers = call_user_func( $this->callback, $hook_args ); 139 | if ( false === $modifiers ) { 140 | return; 141 | } 142 | } 143 | 144 | /** 145 | * Sample return formats from the callback 146 | * 147 | * - True/False: If you return false, it won't update anything, if you return true it will continue with the update process 148 | * - 20: Return an int of the object ID to update the transient value for a specific object when the object cache type is being used. If you have transients using modifiers on this object all modifiers will be updated 149 | * - 'blue': Return the string name of the transient modifier you would like to update. This only works for transients using the "transient" storage engine, since it needs the context of the object ID for object type caches 150 | * - [ 10, 20, 30 ]: Return an array of object ID's to update transients on those specific object ID's 151 | * - [ 152 | * 10 => [ 'blue', 'yellow', 'green' ] Update transients with the modifiers blue, yellow, and green in the object ID: 10 153 | * 20 => [ 'blue', 'green' ] Update transients with the modifiers blue, and green in the object ID: 20 154 | * 30 => [] Update all transients within the group on object ID: 30 155 | * ] 156 | */ 157 | if ( is_array( $modifiers ) && ! empty( $modifiers ) ) { 158 | foreach ( $modifiers as $key => $modifier ) { 159 | if ( is_array( $modifier ) && ! empty( $modifier ) ) { 160 | foreach ( $modifier as $key_modifier ) { 161 | $this->dispatch_update( $key_modifier, $key ); 162 | } 163 | } else { 164 | if ( empty( $modifier ) ) { 165 | $modifier = $key; 166 | } 167 | $this->dispatch_update( $modifier ); 168 | } 169 | } 170 | } else { 171 | $this->dispatch_update( $modifiers ); 172 | } 173 | 174 | } 175 | 176 | /** 177 | * Retrieve data from the hook callback and process it for updating 178 | * 179 | * @param mixed|string|int|bool $modifier The data passed back from the callback 180 | * @param null $object_id ID of the object an update needs to be performed for 181 | * 182 | * @access private 183 | */ 184 | private function dispatch_update( $modifier, $object_id = null ) { 185 | 186 | if ( 'transient' !== $this->transient_obj->cache_type ) { 187 | 188 | if ( empty( $object_id ) ) { 189 | $object_id = absint( $modifier ); 190 | $translated_obj_id = true; 191 | } 192 | 193 | if ( empty( $modifier ) || isset( $translated_obj_id ) ) { 194 | $modifier_map = DFM_Transients::get_meta_map( DFM_Transient_Utils::get_meta_type( $this->transient_obj->cache_type ), $object_id, $this->transient_obj->key ); 195 | if ( ! empty( $modifier_map ) && is_array( $modifier_map ) ) { 196 | foreach ( $modifier_map as $modifier => $modifier_key ) { 197 | $this->run_update( $modifier, $object_id ); 198 | } 199 | } else { 200 | $this->run_update( '', $object_id ); 201 | } 202 | } else { 203 | $this->run_update( $modifier, $object_id ); 204 | } 205 | } else { 206 | $this->run_update( $modifier ); 207 | } 208 | 209 | } 210 | 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /includes/class-dfm-transient-scheduler.php: -------------------------------------------------------------------------------- 1 | $transient_args ) { 44 | 45 | // Store unique identifier to array 46 | $this->transient_ids[] = $transient_id; 47 | 48 | $async_update = $transient_args->async_updates; 49 | 50 | // If the transient has update hooks associated with it, build the hook for it to run. 51 | if ( false !== $transient_args->update_hooks ) { 52 | 53 | // If there are multiple hooks where this should fire, loop through all of them, and build a hook for each. 54 | if ( is_array( $transient_args->update_hooks ) ) { 55 | foreach ( $transient_args->update_hooks as $hook_name => $callback ) { 56 | new DFM_Transient_Hook( $transient_id, $transient_args, $hook_name, $async_update, $callback ); 57 | } 58 | } else { 59 | new DFM_Transient_Hook( $transient_id, $transient_args, $transient_args->update_hooks, $async_update ); 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | // Kick off generation of async processing hooks. 67 | $this->post_processing_hooks(); 68 | 69 | } 70 | 71 | /** 72 | * Build a hook for an async process for each of the transients to grab onto 73 | * 74 | * @return void 75 | * @access public 76 | */ 77 | private function post_processing_hooks() { 78 | foreach ( $this->transient_ids as $transient_id ) { 79 | add_action( 'admin_post_nopriv_dfm_' . $transient_id, array( $this, 'run_update' ) ); 80 | } 81 | } 82 | 83 | /** 84 | * Handle the update for async data processing 85 | * 86 | * @return void 87 | * @access public 88 | */ 89 | public function run_update() { 90 | 91 | $transient_name = empty( $_POST['transient_name'] ) ? false : sanitize_text_field( $_POST['transient_name'] ); 92 | $modifier = empty( $_POST['modifier'] ) ? '' : sanitize_text_field( $_POST['modifier'] ); 93 | $object_id = empty( $_POST['object_id'] ) ? null : sanitize_text_field( $_POST['object_id'] ); 94 | $nonce = empty( $_POST['_nonce'] ) ? '' : sanitize_text_field( $_POST['_nonce'] ); 95 | $lock_key = empty( $_POST['lock_key'] ) ? '' : sanitize_text_field( $_POST['lock_key'] ); 96 | 97 | // Bail if a transient name wasn't passed for some reason 98 | if ( empty( $transient_name ) ) { 99 | return; 100 | } 101 | 102 | $verify_nonce = new DFM_Async_Nonce( $transient_name ); 103 | 104 | // Bail if we couldn't verify the nonce as legit 105 | if ( false === $verify_nonce->verify( $nonce ) ) { 106 | return; 107 | } 108 | 109 | $transient_obj = new DFM_Transients( $transient_name, $modifier, $object_id ); 110 | 111 | if ( 'transient' !== $transient_obj->transient_object->cache_type && empty( $object_id ) ) { 112 | $object_id = absint( $modifier ); 113 | } 114 | 115 | // Bail if another process is already trying to update this transient. 116 | if ( $transient_obj->is_locked() && ! $transient_obj->owns_lock( $lock_key ) ) { 117 | return; 118 | } 119 | 120 | if ( ! $transient_obj->is_locked() ) { 121 | $transient_obj->lock_update(); 122 | } 123 | 124 | $data = call_user_func( $transient_obj->transient_object->callback, $modifier, $object_id ); 125 | $transient_obj->set( $data ); 126 | 127 | $transient_obj->unlock_update(); 128 | 129 | } 130 | 131 | } 132 | 133 | endif; 134 | 135 | new DFM_Transient_Scheduler(); 136 | -------------------------------------------------------------------------------- /includes/class-dfm-transient-utils.php: -------------------------------------------------------------------------------- 1 | transient = $transient; 114 | $this->modifier = $modifier; 115 | $this->object_id = $object_id; 116 | $this->transient_object = $dfm_transients[ $this->transient ]; 117 | $this->lock_key = uniqid( 'dfm_lt_' ); 118 | self::$prefix = apply_filters( 'dfm_transient_prefix', 'dfm_transient_', $this->transient, $this->modifier, $this->object_id ); 119 | 120 | /** 121 | * For backwards compatibility, use the modifier value as the object ID if no ID is supplied, but it's an object type cache. 122 | */ 123 | if ( 'transient' !== $this->transient_object->cache_type && empty( $object_id ) ) { 124 | $this->object_id = absint( $modifier ); 125 | $this->bc_modifier = $modifier; 126 | $this->modifier = ''; 127 | } 128 | 129 | /** 130 | * Generate the cache key after we've applied the backward compat fix above ^ 131 | */ 132 | $this->key = $this->cache_key(); 133 | 134 | } 135 | 136 | /** 137 | * Method to retrieve a transient from the cache or DB. 138 | * 139 | * @return mixed|WP_Error|string|array 140 | * @access public 141 | */ 142 | public function get() { 143 | 144 | if ( 'transient' === $this->transient_object->cache_type ) { 145 | $this->get_from_transient(); 146 | } else { 147 | $meta_type = DFM_Transient_Utils::get_meta_type( $this->transient_object->cache_type ); 148 | if ( is_wp_error( $meta_type ) ) { 149 | return $meta_type; 150 | } else { 151 | $this->get_from_meta( $meta_type ); 152 | } 153 | } 154 | 155 | switch ( $this->transient_object->cache_type ) { 156 | case 'transient': 157 | return $this->get_from_transient(); 158 | break; 159 | case 'post_meta': 160 | return $this->get_from_meta( 'post' ); 161 | break; 162 | case 'term_meta': 163 | return $this->get_from_meta( 'term' ); 164 | break; 165 | case 'user_meta': 166 | return $this->get_from_meta( 'user' ); 167 | break; 168 | default: 169 | return new WP_Error( 'invalid-cache-type', __( 'When registering your transient, you used an invalid cache type. Valid options are transient, post_meta, term_meta.', 'dfm-transients' ) ); 170 | } 171 | 172 | } 173 | 174 | /** 175 | * This method handles storing transient data to the database or object cache 176 | * 177 | * @param string|array $data The data to add to the transient 178 | * @access public 179 | * @return mixed|WP_Error|Void 180 | * @throws Exception 181 | */ 182 | public function set( $data ) { 183 | 184 | if ( false === $data || is_wp_error( $data ) ) { 185 | $this->facilitate_retry(); 186 | return; 187 | } 188 | 189 | if ( 'transient' === $this->transient_object->cache_type ) { 190 | $this->save_to_transient( $data ); 191 | } else { 192 | $meta_type = DFM_Transient_Utils::get_meta_type( $this->transient_object->cache_type ); 193 | if ( is_wp_error( $meta_type ) ) { 194 | return $meta_type; 195 | } else { 196 | $this->save_to_metadata( $data, $meta_type ); 197 | } 198 | } 199 | 200 | } 201 | 202 | /** 203 | * This method handles the deletion of a transient 204 | * 205 | * @return mixed|void|WP_Error 206 | * @access public 207 | * @throws Exception 208 | */ 209 | public function delete() { 210 | 211 | if ( 'transient' === $this->transient_object->cache_type ) { 212 | $this->delete_from_transient(); 213 | } else { 214 | $meta_type = DFM_Transient_Utils::get_meta_type( $this->transient_object->cache_type ); 215 | if ( is_wp_error( $meta_type ) ) { 216 | return $meta_type; 217 | } else { 218 | $this->delete_from_metadata( $meta_type ); 219 | } 220 | } 221 | 222 | } 223 | 224 | /** 225 | * Locks the ability to update the transient data. This will prevent race conditions. 226 | * 227 | * @return void 228 | * @access public 229 | */ 230 | public function lock_update() { 231 | set_transient( 'dfm_lt_' . $this->key , $this->lock_key, 400 ); 232 | } 233 | 234 | /** 235 | * Unlocks the ability to update the transient data. 236 | * 237 | * @return void 238 | * @access public 239 | */ 240 | public function unlock_update() { 241 | delete_transient( 'dfm_lt_' . $this->key ); 242 | } 243 | 244 | /** 245 | * Returns true or false for if the transient is locked for updates. 246 | * 247 | * @return bool 248 | * @access public 249 | */ 250 | public function is_locked() { 251 | $this->lock = get_transient( 'dfm_lt_' . $this->key ); 252 | return (bool) $this->lock; 253 | } 254 | 255 | /** 256 | * Returns true or false if the method that is trying to update the transient owns the lock for it. 257 | * 258 | * @param string $lock_key The key to compare 259 | * @return bool 260 | * @access public 261 | */ 262 | public function owns_lock( $lock_key ) { 263 | 264 | if ( empty( $this->lock ) ) { 265 | $this->lock = get_transient( 'dfm_lt_' . $this->key ); 266 | } 267 | 268 | if ( $this->lock === $lock_key ) { 269 | return true; 270 | } else { 271 | return false; 272 | } 273 | 274 | } 275 | 276 | /** 277 | * Handles retrieving data from a transient 278 | * 279 | * @return mixed|string|array 280 | * @access private 281 | */ 282 | private function get_from_transient() { 283 | 284 | $data = get_transient( $this->key ); 285 | 286 | return $this->get_transient_data( $data ); 287 | 288 | } 289 | 290 | /** 291 | * Handles retrieving data from post_meta or term_meta 292 | * 293 | * @param string $type The object type the meta is attached to 294 | * @return mixed string|array 295 | * @access private 296 | */ 297 | private function get_from_meta( $type ) { 298 | 299 | $data = get_metadata( $type, $this->object_id, $this->key, true ); 300 | 301 | if ( empty( $data ) ) { 302 | $data_exists = metadata_exists( $type, $this->modifier, $this->key ); 303 | $data = ( false === $data_exists ) ? false : $data; 304 | } 305 | 306 | return $this->get_transient_data( $data ); 307 | 308 | } 309 | 310 | /** 311 | * Handles the low level retrieval and rehydrating of data if necessary for all cache types 312 | * 313 | * @param mixed $data The data beind returned from the get_transient or get_metadata functions 314 | * @return bool|mixed 315 | * @access private 316 | */ 317 | private function get_transient_data( $data ) { 318 | 319 | // Check to see if we set a backwards compatible modifier 320 | if ( 'transient' !== $this->transient_object->cache_type && ! empty( $this->bc_modifier ) ) { 321 | $modifier = $this->bc_modifier; 322 | } else { 323 | $modifier = $this->modifier; 324 | } 325 | 326 | if ( false === $data || ( defined( 'DFM_TRANSIENTS_HOT_RELOAD' ) && true === DFM_TRANSIENTS_HOT_RELOAD ) ) { 327 | 328 | if ( true === $this->doing_retry() ) { 329 | return false; 330 | } 331 | $data = call_user_func( $this->transient_object->callback, $modifier, $this->object_id ); 332 | $this->set( $data ); 333 | } elseif ( $this->is_expired( $data ) && ! $this->is_locked() ) { 334 | $this->lock_update(); 335 | if ( $this->should_soft_expire() ) { 336 | new DFM_Async_Handler( $this->transient, $modifier, $this->object_id, $this->lock_key ); 337 | } else { 338 | $data = call_user_func( $this->transient_object->callback, $modifier, $this->object_id ); 339 | $this->set( $data ); 340 | $this->unlock_update(); 341 | } 342 | } 343 | 344 | if ( $this->should_expire() && is_array( $data ) && array_key_exists( 'data', $data ) ) { 345 | $data = $data['data']; 346 | } 347 | 348 | return $data; 349 | 350 | } 351 | 352 | /** 353 | * Handles saving of data for a transient storage type 354 | * 355 | * @param string|array $data The data to save to the transient 356 | * @return void 357 | * @access private 358 | */ 359 | private function save_to_transient( $data ) { 360 | 361 | $expiration = 0; 362 | 363 | if ( $this->should_expire() ) { 364 | $expiration = $this->transient_object->expiration; 365 | if ( $this->should_soft_expire() ) { 366 | // Set expiration to a year if we aren't using an object cache, so the transient 367 | // isn't autoloaded from the database 368 | $expiration = ( true === wp_using_ext_object_cache() ) ? 0 : YEAR_IN_SECONDS; 369 | $data = array( 370 | 'data' => $data, 371 | 'expiration' => time() + (int) $this->transient_object->expiration, 372 | ); 373 | } 374 | } 375 | 376 | set_transient( $this->key, $data, $expiration ); 377 | 378 | } 379 | 380 | /** 381 | * Handles saving data for meta storage engine 382 | * 383 | * @param string|array $data The data to save 384 | * @param string $type The object type the meta is connected to 385 | * 386 | * @access private 387 | * @return void 388 | */ 389 | private function save_to_metadata( $data, $type ) { 390 | 391 | if ( $this->should_expire() ) { 392 | $data = array( 393 | 'data' => $data, 394 | 'expiration' => time() + (int) $this->transient_object->expiration, 395 | ); 396 | } 397 | 398 | $r = update_metadata( $type, $this->object_id, $this->key, $data ); 399 | 400 | if ( ! empty( $this->modifier ) ) { 401 | $this->add_meta_map( $type, $this->transient_object->key ); 402 | } 403 | 404 | } 405 | 406 | /** 407 | * Deletes a transient stored in the default transient storage engine 408 | * 409 | * @access private 410 | * @uses delete_transient() 411 | * @return void 412 | */ 413 | private function delete_from_transient() { 414 | delete_transient( $this->key ); 415 | } 416 | 417 | /** 418 | * Deletes a transient stored in metadata 419 | * 420 | * @param string $type The object type related to the metadata 421 | * @uses delete_metadata() 422 | * @return void 423 | * @access private 424 | */ 425 | private function delete_from_metadata( $type ) { 426 | if ( ! empty( $this->modifier ) ) { 427 | $meta_map = $this->get_meta_map( $type, $this->object_id, $this->key ); 428 | if ( ! empty( $meta_map ) && is_array( $meta_map ) ) { 429 | foreach ( $meta_map as $key ) { 430 | delete_metadata( $type, $this->object_id, $key ); 431 | } 432 | } 433 | } 434 | delete_metadata( $type, $this->object_id, $this->key ); 435 | } 436 | 437 | /** 438 | * Add the transient keys to the mapping so they can be found easily later 439 | * 440 | * @param string $type The type of meta to store the map in 441 | * @param string $transient_key The key for the transient group to create the map key off of 442 | * 443 | * @return void 444 | * @access private 445 | */ 446 | private function add_meta_map( $type, $transient_key ) { 447 | 448 | $map = get_metadata( $type, $this->object_id, self::meta_map_key( $transient_key ), true ); 449 | if ( ! empty( $map ) ) { 450 | $map[ $this->modifier ] = $this->key; 451 | } else { 452 | $map = [ $this->modifier => $this->key ]; 453 | } 454 | 455 | update_metadata( $type, $this->object_id, $this->meta_map_key( $transient_key ), array_unique( $map ) ); 456 | 457 | } 458 | 459 | /** 460 | * Return a map of meta keys for all of the modifiers for a transient registered in the group 461 | * 462 | * @param string $type The meta type to use for storage 463 | * @param int $object_id The ID of the object to store the meta map in 464 | * @param string $transient_key Key for the transient group the transient belongs to 465 | * 466 | * @return array 467 | * @access public 468 | * @static 469 | */ 470 | public static function get_meta_map( $type, $object_id, $transient_key ) { 471 | 472 | if ( is_wp_error( $type ) ) { 473 | return array(); 474 | } 475 | 476 | $map = get_metadata( $type, $object_id, self::meta_map_key( $transient_key ), true ); 477 | if ( empty( $map ) ) { 478 | $map = []; 479 | } 480 | 481 | return $map; 482 | 483 | } 484 | 485 | /** 486 | * Return the meta map key for the transient group 487 | * 488 | * @param string $transient_key Transient key for the transient group 489 | * 490 | * @return string 491 | * @access public 492 | * @static 493 | */ 494 | public static function meta_map_key( $transient_key ) { 495 | return self::$prefix . $transient_key . '_map'; 496 | } 497 | 498 | /** 499 | * If a callback function fails to return the correct data, this will store the stale data back into the 500 | * transient, and then set the expiration of the data at an exponential scale, so we are not constantly 501 | * retrying to get the data (if an API is down or something). 502 | * 503 | * @access private 504 | * @return void 505 | * @throws Exception 506 | */ 507 | private function facilitate_retry() { 508 | 509 | // Set flag while doing a retry to prevent infinite loops. 510 | $this->doing_retry = true; 511 | 512 | // Retrieve the stale data. 513 | $current_data = $this->get(); 514 | 515 | // If there is nothing already stored for the transient, bail. 516 | if ( false === $current_data ) { 517 | return; 518 | } 519 | 520 | // Store the expiration set when registering the transient. Our timeout should not exceed this number. 521 | $max_expiration = $this->transient_object->expiration; 522 | 523 | // Retrieve the cache fail amount from the cache 524 | $failed_num = wp_cache_get( $this->key . '_failed', 'dfm_transients_retry' ); 525 | 526 | // Default to 1 failure if there's nothing set, or it's set to zero. This is so it doesn't mess with 527 | // the `pow` func. 528 | if ( false === $failed_num || 0 === $failed_num ) { 529 | $failures = 1; 530 | } else { 531 | $failures = $failed_num + 1; 532 | } 533 | 534 | // Generate the new expiration time. This essentially just multiplies the amount of failures by itself, and 535 | // then multiplies it by one minute to get the expiration, so if it is retrying it for the 5th time, it will 536 | // do 5*5 (which is 25) so it will set the retry to 25 minutes. 537 | $new_expiration = ( pow( $failures, 2 ) * MINUTE_IN_SECONDS ); 538 | 539 | // Only set the new expiration if it's less than the original registered expiration. 540 | if ( $new_expiration < $max_expiration ) { 541 | $this->transient_object->expiration = absint( $new_expiration ); 542 | } 543 | 544 | // Save the stale data with the new expiration 545 | $this->set( $current_data ); 546 | 547 | // Add 1 to the the failures in the cache. 548 | wp_cache_set( $this->key . '_failed', $failures, 'dfm_transients_retry', DAY_IN_SECONDS ); 549 | 550 | } 551 | 552 | /** 553 | * Hashes storage key 554 | * 555 | * @param string $key Name of the key to hash 556 | * @return string 557 | * @access private 558 | */ 559 | private function hash_key( $key ) { 560 | 561 | $hashed_key = md5( $key ); 562 | 563 | if ( in_array( $this->transient_object->cache_type, $this->meta_types, true ) ) { 564 | /** 565 | * If the storage type is *_meta then prepend the prefix after we hash so we can 566 | * still find it for debugging 567 | */ 568 | $hashed_key = self::$prefix . $hashed_key; 569 | } 570 | 571 | return $hashed_key; 572 | 573 | } 574 | 575 | /** 576 | * Generates the cache key to be used for getting and setting transient data 577 | * 578 | * @return string 579 | * @access private 580 | */ 581 | private function cache_key() { 582 | 583 | $key = $this->transient_object->key; 584 | 585 | if ( ! empty( $this->modifier ) ) { 586 | // Add the unique modifier to the key for regular transients 587 | $key = $key . '_' . $this->modifier; 588 | } 589 | 590 | if ( $this->should_hash() ) { 591 | return $this->hash_key( $key ); 592 | } 593 | 594 | if ( in_array( $this->transient_object->cache_type, $this->meta_types, true ) ) { 595 | // Add the prefix to transients stored in meta only so they can be identified 596 | $key = self::$prefix . $key; 597 | } 598 | 599 | return $key; 600 | 601 | } 602 | 603 | /** 604 | * Whether or not we should hash the storage key 605 | * 606 | * @return bool 607 | * @access private 608 | */ 609 | private function should_hash() { 610 | return (bool) $this->transient_object->hash_key; 611 | } 612 | 613 | /** 614 | * Whether or not the transient data should expire 615 | * 616 | * @return bool 617 | * @access private 618 | */ 619 | private function should_expire() { 620 | return (bool) $this->transient_object->expiration; 621 | } 622 | 623 | /** 624 | * Whether or not the transient should expire softly 625 | * 626 | * @return bool 627 | * @access private 628 | */ 629 | private function should_soft_expire() { 630 | return (bool) $this->transient_object->soft_expiration; 631 | } 632 | 633 | /** 634 | * Whether or not the transient data has expired 635 | * 636 | * @param array|string $data The data to check if it's expired 637 | * @access private 638 | * @return bool 639 | */ 640 | protected function is_expired( $data ) { 641 | if ( ! empty( $this->transient_object->expiration ) && is_array( $data ) && $data['expiration'] < time() ) { 642 | return true; 643 | } 644 | return false; 645 | } 646 | 647 | /** 648 | * Whether or not a retry is occurring 649 | * 650 | * @access private 651 | * @return bool 652 | */ 653 | private function doing_retry() { 654 | return (bool) $this->doing_retry; 655 | } 656 | 657 | } 658 | 659 | endif; 660 | -------------------------------------------------------------------------------- /includes/cli.php: -------------------------------------------------------------------------------- 1 | 14 | * : Name of the transient you want to retrieve information about 15 | * 16 | * [...] 17 | * : List of modifiers to get data about 18 | * 19 | * [--format] 20 | * : Render the output in a particular format 21 | * --- 22 | * default: table 23 | * options: 24 | * - table 25 | * - csv 26 | * - ids 27 | * - json 28 | * - count 29 | * - yaml 30 | * --- 31 | * 32 | * [--limit] 33 | * : The amount of transients to retrieve. Pass -1 for no limit. 34 | * --- 35 | * default: 100 36 | * --- 37 | * 38 | * [--fields] 39 | * : The fields you would like to return 40 | * 41 | * ## EXAMPLES 42 | * 43 | * $wp dfm-transients get dfm_current_standout_count 44 | * +----------+------+ 45 | * | modifier | data | 46 | * +----------+------+ 47 | * | | 0 | 48 | * +----------+------+ 49 | * 50 | * $wp dfm-transients get term_posts all 51 | * +----------+------------------------------------------------------------------------------------------------------------------+ 52 | * | modifier | data | 53 | * +----------+------------------------------------------------------------------------------------------------------------------+ 54 | * | 7681 | [2187069,2189051,2188653,2188525,2188689,2188302,2188435,2188561,2188519,2188486,2188180,2188052,2188378,2187293 | 55 | * | | ,2187831,2185712,2186525,2186643,2186373,2186221] | 56 | * | 4687 | [2188121,2187831,2187084,2185789,2185208,2185126,2185123,2185003,2183357,2183326,2183276,2183089,2183081,2183060 | 57 | * | | ,2182991,2181531,2180932,2180486,2177749,2179168] | 58 | * | 1797 | [2186592,2177875,2170239,2162981,2155404,2148740] | 59 | * | 8732 | [2188653,2185461,2183384,2182193,2178326,2175819,2174603,2170396,2168493,2167170,2163516,2160893,2158586,2156073 | 60 | * | | ,2154094,2151996,2149292,2147779,2147211,2147178] | 61 | * | 8624 | [2181074,2173277,2157496,2149817,2138319] | 62 | * +----------+------------------------------------------------------------------------------------------------------------------+ 63 | * 64 | * $wp dfm-transients get term_posts all --fields=modifier --format=ids 65 | * 97 7681 4687 1797 8732 8624 98 9372 7682 48 94 75 66 30 15 7629 40 53 59 36 66 | * 67 | * ## AVAILABLE FIELDS 68 | * * modifier 69 | * * data 70 | * 71 | * @param array $args 72 | * @param $assoc_args 73 | */ 74 | public function get( $args, $assoc_args ) { 75 | 76 | $transient_name = array_shift( $args ); 77 | $modifiers = $args; 78 | 79 | $this->supported_props = array( 'modifier', 'data' ); 80 | 81 | $options = wp_parse_args( $assoc_args, array( 82 | 'format' => '', 83 | 'limit' => 100, 84 | ) 85 | ); 86 | 87 | if ( empty( $transient_name ) ) { 88 | parent::error( 'A transient name must be passed' ); 89 | } 90 | 91 | $transient_obj = new DFM_Transients( $transient_name, '' ); 92 | $transient_type = $transient_obj->transient_object->cache_type; 93 | $transient_key = $transient_obj->key; 94 | 95 | switch ( $options['format'] ) { 96 | case 'ids': 97 | $data = $this->get_all_modifiers( $transient_key, $transient_type, false, $options['limit'] ); 98 | break; 99 | case 'count': 100 | $data = $this->get_all_modifiers( $transient_key, $transient_type, true ); 101 | $data = ( ! empty( $data ) && is_array( $data ) ) ? $data[0] : 0; 102 | break; 103 | default: 104 | if ( 'all' === $modifiers[0] ) { 105 | $modifier_keys = $this->get_all_modifiers( $transient_key, $transient_type, false, $options['limit'] ); 106 | } elseif ( ! empty( $modifiers ) ) { 107 | $modifier_keys = $modifiers; 108 | } else { 109 | $data = array( 110 | array( 111 | 'modifier' => '', 112 | 'data' => dfm_get_transient( $transient_name ), 113 | ), 114 | ); 115 | } 116 | 117 | if ( ! isset( $data ) ) { 118 | 119 | $data = array(); 120 | 121 | if ( isset( $modifier_keys ) && ! empty( $modifier_keys ) && is_array( $modifier_keys ) ) { 122 | foreach ( $modifier_keys as $modifier_key ) { 123 | $transient_obj->modifier = absint( $modifier_key ); 124 | $data[] = array( 125 | 'modifier' => $modifier_key, 126 | 'data' => $transient_obj->get(), 127 | ); 128 | } 129 | } 130 | } 131 | break; 132 | } 133 | 134 | if ( 'count' !== $options['format'] ) { 135 | $this->format_output( $data, $assoc_args ); 136 | parent::line(); 137 | } else { 138 | parent::success( sprintf( '%d transients found', $data ) ); 139 | } 140 | 141 | } 142 | 143 | /** 144 | * Sets data for a particular DFM Transient 145 | * 146 | * ## OPTIONS 147 | * 148 | * : Name of the transient you would like to set data for 149 | * 150 | * [...] 151 | * : List of modifiers you want to update the transient data for 152 | * 153 | * [--data=] 154 | * : The new data you want to store in the transient 155 | * 156 | * ## EXAMPLES 157 | * 158 | * $wp dfm-transients set my_transient --data="test" 159 | * Successfully updated the my_transient transient 160 | * 161 | * $wp dfm-transients set term_posts $(wp dfm-transients get term_posts --fields=modifier --format=ids) --data="test" 162 | * Updating Transients 100% [=============================================] 0:00 / 0:00 163 | * Success: Successfully updated 20 transients 164 | * 165 | * @param $args 166 | * @param $assoc_args 167 | */ 168 | public function set( $args, $assoc_args ) { 169 | 170 | $transient_name = array_shift( $args ); 171 | $modifiers = $args; 172 | 173 | if ( empty( $transient_name ) ) { 174 | parent::error( 'A transient name must be passed' ); 175 | } 176 | 177 | $data = \WP_CLI\Utils\get_flag_value( $assoc_args, 'data', '' ); 178 | 179 | if ( ! empty( $modifiers ) && is_array( $modifiers ) ) { 180 | 181 | if ( 10 < count( $modifiers ) ) { 182 | $progress = \WP_CLI\Utils\make_progress_bar( 'Updating Transients', count( $modifiers ) ); 183 | } 184 | 185 | foreach ( $modifiers as $modifier ) { 186 | dfm_set_transient( $transient_name, $data, absint( $modifier ) ); 187 | if ( isset( $progress ) ) { 188 | $progress->tick(); 189 | } 190 | } 191 | 192 | if ( isset( $progress ) ) { 193 | $progress->finish(); 194 | } 195 | 196 | parent::success( sprintf( 'Successfully updated %d transients', count( $modifiers ) ) ); 197 | 198 | } else { 199 | dfm_set_transient( $transient_name, $data, '' ); 200 | parent::success( sprintf( 'Successfully updated the %s transient', $transient_name ) ); 201 | } 202 | 203 | } 204 | 205 | /** 206 | * Deletes some particular DFM Transients 207 | * 208 | * ## OPTIONS 209 | * 210 | * : Name of the transient you would like to delete data for 211 | * 212 | * [...] 213 | * : List of modifiers you want to delete the transients for 214 | * 215 | * ## EXAMPLES 216 | * 217 | * $wp dfm-transients delete my_transient 218 | * Success: Successfully deleted transient: my_transient 219 | * 220 | * $wp dfm-transients delete term_posts $(wp dfm-transients get term_posts --fields=modifier --format=ids) 221 | * Deleting transients 100% [=============================================] 0:00 / 0:00 222 | * Success: Successfully deleted 20 transients 223 | * 224 | * @param array $args 225 | * @param array $assoc_args 226 | */ 227 | public function delete( $args, $assoc_args ) { 228 | 229 | $transient_name = array_shift( $args ); 230 | $modifiers = $args; 231 | 232 | if ( empty( $transient_name ) ) { 233 | parent::error( 'A transient name must be passed' ); 234 | } 235 | 236 | if ( empty( $modifiers ) ) { 237 | dfm_delete_transient( $transient_name ); 238 | parent::success( sprintf( 'Successfully deleted transient: %s', $transient_name ) ); 239 | } else { 240 | 241 | if ( count( $modifiers ) > 10 ) { 242 | $progress = \WP_CLI\Utils\make_progress_bar( 'Deleting transients', count( $modifiers ) ); 243 | } 244 | 245 | if ( is_array( $modifiers ) ) { 246 | 247 | foreach ( $modifiers as $modifier ) { 248 | 249 | dfm_delete_transient( $transient_name, $modifier ); 250 | 251 | if ( isset( $progress ) ) { 252 | $progress->tick(); 253 | } 254 | 255 | } 256 | 257 | if ( isset( $progress ) ) { 258 | $progress->finish(); 259 | } 260 | 261 | } 262 | 263 | parent::success( sprintf( 'Successfully deleted %d transients', count( $modifiers ) ) ); 264 | 265 | } 266 | 267 | } 268 | 269 | /** 270 | * Lists all of the registered transients through DFM Transients 271 | * 272 | * ## OPTIONS 273 | * [...] 274 | * : Optionally pass the names of the transients you want to get information about 275 | * 276 | * [--fields] 277 | * : Fields to return 278 | * --- 279 | * default: all 280 | * options: 281 | * - key 282 | * - hash_key 283 | * - cache_type 284 | * - async_updates 285 | * - expiration 286 | * - soft_expiration 287 | * --- 288 | * 289 | * [--format] 290 | * : Render the output in a particular format 291 | * --- 292 | * default: table 293 | * options: 294 | * - table 295 | * - csv 296 | * - ids 297 | * - json 298 | * - yaml 299 | * --- 300 | * 301 | * [--=] 302 | * : One or more fields to filter the list with 303 | * 304 | * ## EXAMPLES 305 | * 306 | * $ wp dfm-transients list 307 | * +----------------------------+----------+------------+---------------+------------+-----------------+ 308 | * | key | hash_key | cache_type | async_updates | expiration | soft_expiration | 309 | * +----------------------------+----------+------------+---------------+------------+-----------------+ 310 | * | dfm_instagram_api_feed | | transient | | 3600 | 1 | 311 | * | dfm_current_standout_count | | transient | | 3600 | 1 | 312 | * | author_featured_articles | | post_meta | 1 | | | 313 | * | nav_menu | | transient | 1 | | | 314 | * +----------------------------+----------+------------+---------------+------------+-----------------+ 315 | * 316 | * $ wp dfm-transients list dfm_current_standout_count author_featured_articles --fields=key,cache_type,async_updates 317 | * +----------------------------+------------+---------------+ 318 | * | key | cache_type | async_updates | 319 | * +----------------------------+------------+---------------+ 320 | * | dfm_current_standout_count | transient | | 321 | * | author_featured_articles | post_meta | 1 | 322 | * +----------------------------+------------+---------------+ 323 | * 324 | * $wp dfm-transients list --async_updates=1 --fields=key,cache_type,async_updates 325 | * +--------------------------+------------+---------------+ 326 | * | key | cache_type | async_updates | 327 | * +--------------------------+------------+---------------+ 328 | * | author_featured_articles | post_meta | 1 | 329 | * | nav_menu | transient | 1 | 330 | * | author_list_query | transient | 1 | 331 | * | term_posts | term_meta | 1 | 332 | * +--------------------------+------------+---------------+ 333 | * 334 | * @param $args 335 | * @param $assoc_args 336 | */ 337 | public function list( $args, $assoc_args ) { 338 | 339 | global $dfm_transients; 340 | 341 | $transients = $dfm_transients; 342 | 343 | $this->supported_props = array( 'key', 'hash_key', 'cache_type', 'async_updates', 'expiration', 'soft_expiration' ); 344 | 345 | if ( empty( $dfm_transients ) ) { 346 | parent::error( 'Looks like you don\'t have any transients registered' ); 347 | } 348 | 349 | $transient_names = ( isset( $args ) ) ? $args : array(); 350 | 351 | if ( ! empty( $transient_names ) ) { 352 | $transients = array_intersect_key( $transients, array_flip( $transient_names ) ); 353 | } 354 | 355 | if ( ! empty( $assoc_args ) ) { 356 | $filter_args = array_intersect_key( $assoc_args, array_flip( $this->supported_props ) ); 357 | $transients = wp_list_filter( $transients, $filter_args ); 358 | } 359 | 360 | if ( empty( $transients ) ) { 361 | parent::error( 'No transients found with this criteria' ); 362 | } 363 | 364 | $this->format_output( $transients, $assoc_args ); 365 | 366 | } 367 | 368 | /** 369 | * Method to retrieve modifier ID's or the count of modifiers 370 | * 371 | * @param string $meta_key Name of the meta key to look for 372 | * @param string $type Meta type so we know which table to search in 373 | * @param bool $count Whether or not we should return the total count 374 | * @param bool|int $limit Whether or not we should limit results, and if so what that limit is 375 | * 376 | * @return array|bool 377 | */ 378 | private function get_all_modifiers( $meta_key, $type, $count = false, $limit = false ) { 379 | 380 | global $wpdb; 381 | 382 | $object_type = substr( $type, 0, 4 ); 383 | 384 | $table = _get_meta_table( $object_type ); 385 | 386 | $select = ( true === $count ) ? 'count(*)' : $object_type . '_id'; 387 | 388 | $limit = ( false !== $limit && '-1' !== $limit ) ? 'LIMIT ' . absint( $limit ) : ''; 389 | 390 | if ( false === $table ) { 391 | return false; 392 | } 393 | 394 | $modifiers = $wpdb->get_col( $wpdb->prepare( " 395 | SELECT $select 396 | FROM $table 397 | WHERE meta_key='%s' 398 | $limit 399 | ", 400 | $meta_key 401 | ) ); 402 | 403 | return $modifiers; 404 | 405 | } 406 | 407 | /** 408 | * Handles the formatting of output 409 | * 410 | * @param array $transients The data to display 411 | * @param array $assoc_args Args so we know how to display it 412 | */ 413 | private function format_output( $transients, $assoc_args ) { 414 | 415 | if ( ! empty( $assoc_args['fields'] ) ) { 416 | if ( is_string( $assoc_args['fields'] ) ) { 417 | $fields = explode( ',', $assoc_args['fields'] ); 418 | } else { 419 | $fields = $assoc_args['fields']; 420 | } 421 | $fields = array_intersect( $fields, $this->supported_props ); 422 | } else { 423 | $fields = $this->supported_props; 424 | } 425 | 426 | $formatter = new \WP_CLI\Formatter( $assoc_args, $fields ); 427 | $formatter->display_items( $transients ); 428 | 429 | } 430 | 431 | } 432 | 433 | WP_CLI::add_command( 'dfm-transients', 'DFM_Transients_CLI' ); 434 | 435 | } 436 | -------------------------------------------------------------------------------- /includes/template-tags.php: -------------------------------------------------------------------------------- 1 | value storage 13 | * @type bool $hash_key Whether or not we should hash the key for storage 14 | * @type string $cache_type Which storage engine we should use. Options are: transient, post_meta, user_meta, or term_meta 15 | * @type bool|callable $callback Provide callback for updating / populating the data to be stored 16 | * @type bool $async_updates Whether or not we should update this cache asynchronously or not 17 | * @type bool|array $update_hooks Name of the hook where the update function should fire { 18 | * 19 | * Array of hook names that we should update our transients on. Should look like the following: 20 | * 'hook_name' => 'callback' 21 | * All of the arguments from the action are passed to the callback as an array in the order they are passed. 22 | * If the criteria to clear the transient data is not met, your callback should return false, otherwise it 23 | * should return the unique "modifier" for the transient. 24 | * 25 | * } 26 | * @type string $expiration Unix timestamp of when the data should expire 27 | * @type bool $soft_expiration Whether or not we should do a soft expiration on the data 28 | * } 29 | * @throws exception 30 | */ 31 | function dfm_register_transient( $transient, $args = array() ) { 32 | 33 | global $dfm_transients; 34 | 35 | if ( ! is_array( $dfm_transients ) ) { 36 | $dfm_transients = array(); 37 | } 38 | 39 | if ( empty( $args['callback'] ) ) { 40 | throw new Exception( __( 'You must add a callback when registering a transient', 'dfm-transients' ) ); 41 | } 42 | 43 | $default_args = array( 44 | 'key' => $transient, 45 | 'hash_key' => false, 46 | 'cache_type' => 'transient', 47 | 'callback' => false, 48 | 'async_updates' => false, 49 | 'update_hooks' => false, 50 | 'expiration' => false, 51 | 'soft_expiration' => false, 52 | ); 53 | 54 | $args = wp_parse_args( 55 | /** 56 | * Filters the args for registering a transient 57 | * 58 | * @param array $args The arguments we are trying to register 59 | * @param string $transient The name of the transient we are registering 60 | * @return array $args The args array should be returned 61 | */ 62 | apply_filters( 'dfm_transients_registration_args', $args, $transient ), 63 | $default_args 64 | ); 65 | 66 | // Type set to object to stay consistent with other WP globally registered objects such as taxonomies. 67 | $dfm_transients[ $transient ] = (object) $args; 68 | 69 | } 70 | 71 | /** 72 | * Retrieves the data from the transient 73 | * 74 | * @param string $transient The name of the transient that we would like to retrieve. 75 | * @param string|int $modifier The unique modifier for the transient. 76 | * @param null|int $object_id The ID of the object the transient data is related to 77 | * 78 | * @return mixed|WP_Error|array|string 79 | * @throws Exception 80 | */ 81 | function dfm_get_transient( $transient, $modifier = '', $object_id = null ) { 82 | 83 | $transients = new DFM_Transients( 84 | /** 85 | * Filters the name of the transient to retrieve 86 | * 87 | * @param string $transient The name of the transient we want to retrieve 88 | * @param string $modifier The unique modifier for the transient we want to retrieve 89 | * 90 | * @return string $transient The transient name should be returned 91 | */ 92 | apply_filters( 'dfm_transients_get_transient_name', $transient, $modifier ), 93 | 94 | /** 95 | * Filters the unique modifier for the transient 96 | * 97 | * @param string $modifier The unique modifier for the transient we want to retrieve 98 | * @param string $transient The name of the transient we want to retrieve 99 | * 100 | * @return string $modifier The unique modifier should be returned 101 | */ 102 | apply_filters( 'dfm_transients_get_transient_modifier', $modifier, $transient ), 103 | $object_id 104 | ); 105 | 106 | /** 107 | * Filters the data returned 108 | * 109 | * @param mixed|array|object|string|bool $transients the value of the transient being returned 110 | * @param string $transients the name of the transient 111 | * @param string $modifier the unique modifier passed to retrieve the transient data 112 | * 113 | * @return mixed|array|object|string|bool $transients The value of the transient should be returned again 114 | */ 115 | return apply_filters( 'dfm_transients_get_result', $transients->get(), $transient, $modifier ); 116 | 117 | } 118 | 119 | /** 120 | * Handles the setting of data to a particular transient 121 | * 122 | * @param string $transient The name of the transient we would like to set data to 123 | * @param string $data The data we want to set to the transient 124 | * @param string|int $modifier The unique modifier for the transient to be appended to the 125 | * transient name for the key 126 | * @param null|int $object_id ID of the object the transient data is related to 127 | * 128 | * @throws Exception 129 | * @return void 130 | */ 131 | function dfm_set_transient( $transient, $data, $modifier = '', $object_id = null ) { 132 | 133 | $transients = new DFM_Transients( 134 | 135 | /** 136 | * Filters the name of the transient to set 137 | * 138 | * @param string $transient The name of the transient 139 | * @param string $modifier The unique modifier 140 | * @param mixed $data The data you want to save to your transient 141 | * 142 | * @return string $transient The name of the transient to set data to 143 | */ 144 | apply_filters( 'dfm_transients_set_transient_name', $transient, $modifier, $data ), 145 | 146 | /** 147 | * Filters the unique modifier for the transient 148 | * 149 | * @param string $modifier The unique modifier 150 | * @param string $transient The name of the transient we want to save data to 151 | * @param mixed $data The data that we want to save to the transient 152 | * 153 | * @return string $modifier The unique modifier for the transient 154 | */ 155 | apply_filters( 'dfm_transients_set_transient_modifier', $modifier, $transient, $data ), 156 | $object_id 157 | ); 158 | 159 | // Invoke the set method 160 | $transients->set( $data ); 161 | 162 | } 163 | 164 | /** 165 | * Handles the deletion of a single transient 166 | * 167 | * @param string $transient Name of the transient you want to delete. Must match what is set 168 | * when registering the transient. 169 | * @param string|int $modifier Unique modifier for the transient that you want to delete. 170 | * @param null|int $object_id ID of the object the transient data is related to 171 | * 172 | * @return void 173 | * @throws Exception 174 | */ 175 | function dfm_delete_transient( $transient, $modifier = '', $object_id = null ) { 176 | 177 | $transients = new DFM_Transients( 178 | 179 | /** 180 | * Filters the name of the transient to delete 181 | * 182 | * @param string $transient Name of the transient 183 | * @param string $modifier The unique modifier for the transient you want to delete 184 | * @return string $transient Name of the transient to be deleted 185 | */ 186 | apply_filters( 'dfm_transients_delete_transient_name', $transient, $modifier ), 187 | 188 | /** 189 | * Filters the modifier of the transient to delete 190 | * 191 | * @param string $modifier Unique modifier for the transient you want to delete 192 | * @param string $transient Name of the transient to be deleted 193 | * @return string $modifier Unique modifier for the transient to be deleted 194 | */ 195 | apply_filters( 'dfm_transients_delete_transient_modifier', $modifier, $transient ), 196 | $object_id 197 | ); 198 | 199 | // Invoke the delete method 200 | $transients->delete(); 201 | 202 | } 203 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generally-applicable sniffs for WordPress plugins 4 | 5 | 6 | 7 | 8 | 9 | 10 | . 11 | 12 | 13 | 14 | 15 | */node_modules/* 16 | */vendor/* 17 | 18 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | 16 | ./dfm-transients.php 17 | ./includes/ 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | spawn_event(); 11 | 12 | /** 13 | * Not the best assertion, but the return value from wp_safe_remote_post is the response 14 | * from the request, which doesn't really contain any useful data 15 | */ 16 | $this->assertFalse( is_wp_error( $result ) ); 17 | 18 | } 19 | 20 | /** 21 | * Test to make sure we halt execution if the async action is trying to trigger this method 22 | * since that will cause an infinite loop 23 | */ 24 | public function testAsyncRequestNotPosted() { 25 | 26 | $async_obj = new DFM_Async_Handler( 'testTransient', '' ); 27 | $_POST['async_action'] = 'true'; 28 | 29 | $result = $async_obj->spawn_event(); 30 | 31 | $this->assertNull( $result ); 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /tests/test-class-dfm-async-nonce.php: -------------------------------------------------------------------------------- 1 | create(); 10 | $this->assertTrue( is_string( $actual ) ); 11 | $this->assertEquals( 10, strlen( $actual ) ); 12 | } 13 | 14 | /** 15 | * Tests that nonces are actually unique 16 | */ 17 | public function testNonceUnique() { 18 | $first_obj = new DFM_Async_Nonce( 'test' ); 19 | $second_obj = new DFM_Async_Nonce( 'another test' ); 20 | $first = $first_obj->create(); 21 | $second = $second_obj->create(); 22 | $this->assertNotEquals( $first, $second ); 23 | } 24 | 25 | /** 26 | * Tests that the verification of the nonce works correctly 27 | */ 28 | public function testShouldVerifyNonce() { 29 | $nonce_obj = new DFM_Async_Nonce( 'testing' ); 30 | $nonce = $nonce_obj->create(); 31 | $this->assertTrue( $nonce_obj->verify( $nonce ) ); 32 | } 33 | 34 | /** 35 | * Tests that the any random string doesn't get verified by the nonce check 36 | */ 37 | public function testShouldNotVerifyNonce() { 38 | $nonce_obj = new DFM_Async_Nonce( 'another_test' ); 39 | $this->assertFalse( $nonce_obj->verify( 'some random string' ) ); 40 | } 41 | 42 | /** 43 | * Tests that an older nonce (older than 12 hours) still will get verified 44 | */ 45 | public function testShouldVerifyOldNonce() { 46 | $nonce_obj = new DFM_Async_Nonce( 'some_test' ); 47 | $i = wp_nonce_tick() - 1; 48 | $nonce = substr( wp_hash( $i . 'dfm_some_test' . get_class( $nonce_obj ), 'nonce' ), -12, 10 ); 49 | $this->assertTrue( $nonce_obj->verify( $nonce ) ); 50 | } 51 | 52 | /** 53 | * Tests that a nonce older than 24 hours does not get verified 54 | */ 55 | public function testShouldNotVerifyOldNonce() { 56 | $nonce_obj = new DFM_Async_Nonce( 'some_other_test' ); 57 | $i = wp_nonce_tick() - 2; 58 | $nonce = substr( wp_hash( $i . 'dfm_some_test' . get_class( $nonce_obj ), 'nonce' ), -12, 10 ); 59 | $this->assertFalse( $nonce_obj->verify( $nonce ) ); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /tests/test-class-dfm-transient-hook.php: -------------------------------------------------------------------------------- 1 | 'transient', 11 | 'callback' => '__return_false', 12 | 'key' => 'test', 13 | ]; 14 | 15 | $hook_obj = new DFM_Transient_Hook( 'testName', (object) $transient_object, 'testHook', true ); 16 | $this->assertTrue( has_filter( 'testHook' ) ); 17 | } 18 | 19 | /** 20 | * Test to make sure a non-async update gets updated correctly 21 | */ 22 | public function testNonAsyncHandlerUpdate() { 23 | 24 | $transient_name = 'testNonAsyncHandlerUpdate'; 25 | dfm_register_transient( $transient_name, [ 26 | 'expiration' => HOUR_IN_SECONDS, 27 | 'callback' => function() { 28 | return 'test data'; 29 | }, 30 | ] ); 31 | 32 | global $dfm_transients; 33 | $transient_obj = $dfm_transients[ $transient_name ]; 34 | 35 | $hook_obj = new DFM_Transient_Hook( $transient_name, $transient_obj, 'testHook', false ); 36 | 37 | $hook_obj->spawn(); 38 | 39 | $this->assertEquals( 'test data', get_transient( $transient_name ) ); 40 | 41 | } 42 | 43 | /** 44 | * Tests to see that an update will fail if the update is locked by a different process 45 | */ 46 | public function testUpdateFailureBecauseOfLock() { 47 | 48 | $transient_name = 'testUpdateFailureBecauseOfLock'; 49 | dfm_register_transient( $transient_name, [ 50 | 'callback' => function() { 51 | return 'test data'; 52 | }, 53 | ] ); 54 | 55 | global $dfm_transients; 56 | $transient_obj = $dfm_transients[ $transient_name ]; 57 | 58 | /** 59 | * Lock the update manually 60 | */ 61 | set_transient( 'dfm_lt_' . $transient_name, '1234' ); 62 | 63 | $hook_obj = new DFM_Transient_Hook( $transient_name, $transient_obj, 'testHook', false ); 64 | $hook_obj->spawn(); 65 | 66 | $this->assertFalse( get_transient( $transient_name ) ); 67 | 68 | } 69 | 70 | /** 71 | * Tests to see that a non async update with multiple modifiers gets updated correctly 72 | */ 73 | public function testNonAsyncUpdateMultipleModifiers() { 74 | 75 | $transient_name = 'testNonAsyncUpdateMultipleModifiers'; 76 | dfm_register_transient( $transient_name, [ 77 | 'callback' => function( $modifier ) { 78 | return 'modifier: ' . $modifier; 79 | }, 80 | ] ); 81 | 82 | global $dfm_transients; 83 | $transient_obj = $dfm_transients[ $transient_name ]; 84 | 85 | $hook_obj = new DFM_Transient_Hook( $transient_name, $transient_obj, 'testHook', false, function() { 86 | return [ 1, 2, 3 ]; 87 | } ); 88 | 89 | $hook_obj->spawn(); 90 | 91 | $this->assertEquals( 'modifier: 1', get_transient( $transient_name . '_1' ) ); 92 | 93 | } 94 | 95 | /** 96 | * Tests to make sure the update process gets short-circuited if the callback for the hook 97 | * returns false 98 | */ 99 | public function testShortCircuitHookUpdate() { 100 | 101 | $transient_name = 'testShortCircuitHookUpdate'; 102 | dfm_register_transient( $transient_name, [ 'callback' => '__return_true' ] ); 103 | 104 | global $dfm_transients; 105 | $transient_obj = $dfm_transients[ $transient_name ]; 106 | 107 | $hook_obj = new DFM_Transient_Hook( $transient_name, $transient_obj, 'testHook', false, function() { 108 | return false; 109 | } ); 110 | 111 | $hook_obj->spawn(); 112 | 113 | $this->assertEquals( false, get_transient( $transient_name ) ); 114 | 115 | } 116 | 117 | /** 118 | * Tests to make sure an async update gets scheduled properly 119 | */ 120 | public function testAsyncHandlerUpdate() { 121 | 122 | $transient_object = [ 123 | 'cache_type' => 'transient', 124 | 'callback' => '__return_false', 125 | 'key' => 'test', 126 | ]; 127 | $hook_obj = new DFM_Transient_Hook( 'testTransient', (object) $transient_object, 'testHook', true ); 128 | $hook_obj->spawn(); 129 | 130 | global $wp_filter; 131 | $this->assertEquals( 1, count( $wp_filter['shutdown']->callbacks[10] ) ); 132 | 133 | } 134 | 135 | /** 136 | * Tests to make sure multiple async updated get scheduled properly 137 | */ 138 | public function testMultipleAsyncHandlerUpdates() { 139 | 140 | $transient_object = [ 141 | 'cache_type' => 'transient', 142 | 'callback' => '__return_false', 143 | 'key' => 'test', 144 | ]; 145 | $hook_obj = new DFM_Transient_Hook( 'testTransient', (object) $transient_object, 'testHook', true, function() { 146 | return [ 1, 2, 3 ]; 147 | } ); 148 | 149 | $hook_obj->spawn(); 150 | 151 | global $wp_filter; 152 | $this->assertEquals( 3, count( $wp_filter['shutdown']->callbacks[10] ) ); 153 | 154 | } 155 | 156 | /** 157 | * Test that the hook handler returns the proper data to the callback for post object cache transients without modifiers 158 | */ 159 | public function testPostObjectCacheHandlerUpdate() { 160 | 161 | $transient_name = 'testPostObjectCacheHandlerUpdate'; 162 | $post_id = $this->factory->post->create(); 163 | $return_string = 'Post ID: %d, Modifier: %s'; 164 | dfm_register_transient( $transient_name, [ 165 | 'cache_type' => 'post_meta', 166 | 'callback' => function( $modifier, $post_id ) use ( $return_string ) { 167 | return sprintf( $return_string, $post_id, $modifier ); 168 | } 169 | ] ); 170 | 171 | global $dfm_transients; 172 | $transient_obj = $dfm_transients[ $transient_name ]; 173 | 174 | $hook_obj = new DFM_Transient_Hook( $transient_name, (object) $transient_obj, $transient_name, false, function( $id ) { 175 | return $id[0]; 176 | } ); 177 | 178 | do_action( $transient_name, $post_id ); 179 | 180 | $expected = sprintf( $return_string, $post_id, '' ); 181 | $transient_obj = new DFM_Transients( $transient_name, '', $post_id ); 182 | $actual = get_post_meta( $post_id, $transient_obj->key, true ); 183 | $this->assertEquals( $expected, $actual ); 184 | $this->assertEquals( [], DFM_Transients::get_meta_map( 'post', $post_id, $transient_name ) ); 185 | $this->assertFalse( metadata_exists( 'post', $post_id, 'dfm_transient_' . $transient_name . '_map' ) ); 186 | 187 | } 188 | 189 | /** 190 | * Test that all transients within a transient group are refreshed when just the object ID is returned from the hook callback 191 | */ 192 | public function testTermObjectCacheHandlerWithModifiers() { 193 | 194 | $transient_name = 'testTermObjectCacheHandlerWithModifiers'; 195 | $term_id = $this->factory->category->create(); 196 | $return_string = 'Term ID: %d, Modifier: %s'; 197 | 198 | dfm_register_transient( $transient_name, [ 199 | 'cache_type' => 'term_meta', 200 | 'callback' => function( $modifier, $term_id ) use ( $return_string ) { 201 | return sprintf( $return_string, $term_id, $modifier ); 202 | }, 203 | 'hash_key' => true, 204 | ] ); 205 | 206 | $modifiers = [ 207 | 'rainbow', 208 | 'apples', 209 | 'unicorn', 210 | ]; 211 | 212 | /** 213 | * Add some data to start out with, so we get a map. 214 | */ 215 | foreach ( $modifiers as $modifier ) { 216 | $transient_data = dfm_get_transient( $transient_name, $modifier, $term_id ); 217 | $this->assertEquals( sprintf( $return_string, $term_id, $modifier ), $transient_data ); 218 | } 219 | 220 | $transient_obj = new DFM_Transients( $transient_name, $modifiers[0], $term_id ); 221 | $transient_obj->delete(); 222 | 223 | global $dfm_transients; 224 | $transient_obj = $dfm_transients[ $transient_name ]; 225 | 226 | $hook_obj = new DFM_Transient_Hook( $transient_name, (object) $transient_obj, $transient_name, false, function( $term_id ) { 227 | return $term_id[0]; 228 | } ); 229 | 230 | do_action( $transient_name, $term_id ); 231 | 232 | $transient_keys = DFM_Transients::get_meta_map( 'term', $term_id, $transient_obj->key ); 233 | $this->assertEquals( 3, count( $transient_keys ) ); 234 | 235 | foreach ( $transient_keys as $modifier => $key ) { 236 | $this->assertEquals( sprintf( $return_string, $term_id, $modifier ), get_term_meta( $term_id, $key, true ) ); 237 | } 238 | 239 | } 240 | 241 | /** 242 | * Test that all the transients on a user type cache are cleared only when the modifiers for the 243 | * object ID are passed back in the callback, or if it returns an empty array for the ID. 244 | */ 245 | public function testUserObjectCacheHandlerWithModifiers() { 246 | 247 | $transient_name = 'testUserObjectCacheHandlerWithModifiers'; 248 | $users = [ 249 | $this->factory->user->create(), 250 | $this->factory->user->create(), 251 | $this->factory->user->create(), 252 | $this->factory->user->create(), 253 | ]; 254 | $return_string = 'User ID: %d, Modifier: %s'; 255 | 256 | dfm_register_transient( $transient_name, [ 257 | 'cache_type' => 'user_meta', 258 | 'callback' => function( $modifier, $user_id ) use ( $return_string ){ 259 | return sprintf( $return_string, $user_id, $modifier ); 260 | } 261 | ] ); 262 | 263 | $modifiers = [ 264 | 'blue', 265 | 'purple', 266 | 'test', 267 | ]; 268 | 269 | foreach ( $users as $user ) { 270 | foreach( $modifiers as $modifier ) { 271 | dfm_set_transient( $transient_name, 'test', $modifier, $user ); 272 | } 273 | } 274 | 275 | global $dfm_transients; 276 | $transient_obj = $dfm_transients[ $transient_name ]; 277 | 278 | $hook_obj = new DFM_Transient_Hook( $transient_name, (object) $transient_obj, $transient_name, false, function( $data ) { 279 | return $data[0]; 280 | } ); 281 | 282 | $hook_data = [ 283 | $users[0] => $modifiers, 284 | $users[1] => $modifiers, 285 | $users[2] => array_slice( $modifiers, 0, 2 ), 286 | $users[3] => [], 287 | ]; 288 | 289 | do_action( $transient_name, $hook_data ); 290 | 291 | $transient_keys = []; 292 | foreach ( $users as $user ) { 293 | $transient_keys[ $user ] = DFM_Transients::get_meta_map( 'user', $user, $transient_name ); 294 | } 295 | 296 | foreach ( $transient_keys as $user_id => $keys ) { 297 | $hook_modifiers = $hook_data[ $user_id ]; 298 | if ( is_array( $hook_modifiers ) && ! empty( $hook_modifiers ) ) { 299 | foreach ( $hook_modifiers as $modifier ) { 300 | $this->assertEquals( sprintf( $return_string, $user_id, $modifier ), get_user_meta( $user_id, $keys[ $modifier ], true ) ); 301 | } 302 | } else { 303 | foreach ( $modifiers as $modifier ) { 304 | $this->assertEquals( sprintf( $return_string, $user_id, $modifier ), get_user_meta( $user_id, $keys[ $modifier ], true ) ); 305 | } 306 | } 307 | } 308 | 309 | // Test to make sure the "test" modifier on the second to last user isn't updated since we didn't pass it to the hook in the $hook_data var 310 | $this->assertEquals( 'test', get_user_meta( $users[2], $transient_keys[ $users[2] ]['test'], true ) ); 311 | 312 | } 313 | 314 | } 315 | -------------------------------------------------------------------------------- /tests/test-class-dfm-transient-scheduler.php: -------------------------------------------------------------------------------- 1 | '__return_true', 12 | 'update_hooks' => [ 'test_hook', 'another_test_hook' ], 13 | ] ); 14 | 15 | $scheduler_obj = new DFM_Transient_Scheduler(); 16 | $scheduler_obj->get_transients(); 17 | 18 | $this->assertTrue( has_filter( 'admin_post_nopriv_dfm_' . $transient_name ) ); 19 | 20 | } 21 | 22 | /** 23 | * Don't setup any hooks if no transients are registered 24 | */ 25 | public function testSetupHooksNotRun() { 26 | 27 | global $dfm_transients; 28 | $dfm_transients = []; 29 | 30 | $scheduler_obj = $this->getMockBuilder( 'DFM_Transient_Scheduler' ) 31 | ->setMethods( [ 'post_processing_hooks' ] ) 32 | ->getMock(); 33 | 34 | /** 35 | * Asserts that the post_processing_hooks method never gets called if no transients are 36 | * registered 37 | */ 38 | $scheduler_obj->expects( $this->never() ) 39 | ->method( 'post_processing_hooks' ); 40 | 41 | $actual = $scheduler_obj->get_transients(); 42 | $this->assertNull( $actual ); 43 | 44 | } 45 | 46 | /** 47 | * Test to make sure the update happens successfully 48 | */ 49 | public function testUpdateSuccessful() { 50 | 51 | $transient_name = 'testSetupHooks'; 52 | dfm_register_transient( $transient_name, [ 53 | 'callback' => function( $modifier ) { 54 | return 'modifier: ' . $modifier; 55 | }, 56 | 'async_updates' => true, 57 | ] ); 58 | 59 | $nonce_obj = new DFM_Async_Nonce( $transient_name ); 60 | 61 | $_POST['transient_name'] = $transient_name; 62 | $_POST['modifier'] = '1'; 63 | $_POST['_nonce'] = $nonce_obj->create(); 64 | 65 | $scheduler_obj = new DFM_Transient_Scheduler(); 66 | 67 | $scheduler_obj->run_update(); 68 | 69 | $expected = 'modifier: 1'; 70 | $actual = get_transient( $transient_name . '_1' ); 71 | $this->assertEquals( $expected, $actual ); 72 | 73 | } 74 | 75 | /** 76 | * If no transient name is passed in the headers from the async handler, halt execution 77 | */ 78 | public function testUnsuccessfulUpdateWithoutName() { 79 | 80 | $transient_name = 'testUnsuccessfulUpdateWithoutName'; 81 | dfm_register_transient( $transient_name, [ 82 | 'callback' => function( $modifier ) { 83 | return 'modifier: ' . $modifier; 84 | }, 85 | ] ); 86 | 87 | $nonce_obj = new DFM_Async_Nonce( $transient_name ); 88 | 89 | $_POST['transient_name'] = ''; //This should kill the request 90 | $_POST['modifier'] = '1'; 91 | $_POST['_nonce'] = $nonce_obj->create(); 92 | 93 | $scheduler_obj = new DFM_Transient_Scheduler(); 94 | 95 | $scheduler_obj->run_update(); 96 | 97 | $this->assertFalse( get_transient( $transient_name . '_1' ) ); 98 | 99 | } 100 | 101 | /** 102 | * If the incorrect nonce is passed to the method from the handler, halt execution 103 | */ 104 | public function testUnsuccessfulUpdateWithoutNonce() { 105 | 106 | $transient_name = 'testUnsuccessfulUpdateWithoutNonce'; 107 | dfm_register_transient( $transient_name, [ 108 | 'callback' => function( $modifier ) { 109 | return 'modifier: ' . $modifier; 110 | }, 111 | ] ); 112 | 113 | $nonce_obj = new DFM_Async_Nonce( $transient_name . '_test' ); 114 | 115 | $_POST['transient_name'] = $transient_name; //This should kill the request 116 | $_POST['modifier'] = '1'; 117 | $_POST['_nonce'] = $nonce_obj->create(); 118 | 119 | $scheduler_obj = new DFM_Transient_Scheduler(); 120 | 121 | $scheduler_obj->run_update(); 122 | 123 | $this->assertFalse( get_transient( $transient_name . '_1' ) ); 124 | 125 | } 126 | 127 | /** 128 | * If the transient update process is locked by another process, halt execution 129 | */ 130 | public function testUnsuccessfulUpdateWithoutLock() { 131 | 132 | $transient_name = 'testUnsuccessfulUpdateWithoutLock'; 133 | dfm_register_transient( $transient_name, [ 134 | 'callback' => function( $modifier ) { 135 | return 'modifier: ' . $modifier; 136 | }, 137 | ] ); 138 | 139 | /** 140 | * Manually lock the transient from updating 141 | */ 142 | set_transient( 'dfm_lt_' . $transient_name . '_1', 'somevalue' ); 143 | 144 | $nonce_obj = new DFM_Async_Nonce( $transient_name ); 145 | 146 | $_POST['transient_name'] = $transient_name; //This should kill the request 147 | $_POST['modifier'] = '1'; 148 | $_POST['_nonce'] = $nonce_obj->create(); 149 | 150 | $scheduler_obj = new DFM_Transient_Scheduler(); 151 | 152 | $scheduler_obj->run_update(); 153 | 154 | $this->assertFalse( get_transient( $transient_name . '_1' ) ); 155 | 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /tests/test-class-dfm-transients.php: -------------------------------------------------------------------------------- 1 | setExpectedException( 'Exception', 'You are trying to retrieve a transient that doesn\'t exist' ); 12 | $this->expectException( new DFM_Transients( 'NonExistentTransient', '' ) ); 13 | 14 | } 15 | 16 | /** 17 | * Tests that the update locking feature works as expected 18 | */ 19 | public function testLockUpdate() { 20 | 21 | $transient_name = 'testLockUpdate'; 22 | 23 | $this->register_transient( $transient_name ); 24 | $transient_obj = new DFM_Transients( $transient_name, '' ); 25 | 26 | $expected = $transient_obj->lock_key; 27 | 28 | $transient_obj->lock_update(); 29 | 30 | $actual = get_transient( 'dfm_lt_' . $transient_obj->key ); 31 | 32 | $this->assertEquals( $expected, $actual ); 33 | $this->assertEquals( true, $transient_obj->is_locked() ); 34 | $this->assertEquals( true, $transient_obj->owns_lock( $actual ) ); 35 | 36 | $transient_obj->unlock_update(); 37 | $expected = false; 38 | $actual = get_transient( 'dfm_lt_' . $transient_obj->key );; 39 | 40 | $this->assertEquals( $expected, $actual ); 41 | 42 | } 43 | 44 | /** 45 | * Test that a random string cannot be passed to indicate lock ownership 46 | */ 47 | public function testNotOwnsLock() { 48 | 49 | $transient_name = 'testNotOwnsLock'; 50 | $this->register_transient( $transient_name ); 51 | 52 | $transient_obj = new DFM_Transients( $transient_name, '' ); 53 | 54 | $this->assertEquals( false, $transient_obj->owns_lock( 'test' ) ); 55 | 56 | } 57 | 58 | /** 59 | * Tests that the transient key gets generated correctly with and without a modifier 60 | */ 61 | public function testTransientKey() { 62 | 63 | $method = $this->reflect_method( 'cache_key' ); 64 | 65 | $transient_name = 'testTransientKey'; 66 | $this->register_transient( $transient_name ); 67 | 68 | $transient_obj = new DFM_Transients( $transient_name, '' ); 69 | $this->assertEquals( $transient_name, $method->invoke( $transient_obj ) ); 70 | 71 | $modifier = 'test'; 72 | $transient_obj_with_modifier = new DFM_Transients( $transient_name, $modifier ); 73 | $this->assertEquals( $transient_name . '_' . $modifier, $method->invoke( $transient_obj_with_modifier ) ); 74 | 75 | } 76 | 77 | /** 78 | * Tests the retry method 79 | */ 80 | public function testTransientRetryMethod() { 81 | 82 | $transient_name = 'testTransientRetryMethod'; 83 | $this->register_transient( $transient_name, [ 84 | 'callback' => function() { 85 | return 'test value'; 86 | }, 87 | 'expiration' => HOUR_IN_SECONDS, 88 | 'soft_expiration' => true, 89 | ] ); 90 | 91 | $transient_obj = new DFM_Transients( $transient_name, '' ); 92 | 93 | $actual = $transient_obj->get(); 94 | $expected = 'test value'; 95 | $this->assertEquals( $expected, $actual ); 96 | 97 | $transient_obj->set( false ); 98 | $failed_num = wp_cache_get( $transient_obj->key . '_failed', 'dfm_transients_retry' ); 99 | $this->assertEquals( 1, $failed_num ); 100 | 101 | $this->assertEquals( $expected, $transient_obj->get() ); 102 | 103 | $transient_obj->set( new WP_Error( 'sample-error', 'something happened' ) ); 104 | $this->assertEquals( $expected, $transient_obj->get() ); 105 | $failed_num = wp_cache_get( $transient_obj->key . '_failed', 'dfm_transients_retry' ); 106 | $this->assertEquals( 2, $failed_num ); 107 | 108 | $transient_data = get_transient( $transient_obj->key ); 109 | $this->assertEquals( ( 1 * MINUTE_IN_SECONDS ) + time(), $transient_data['expiration'] ); 110 | 111 | } 112 | 113 | /** 114 | * Tests to make sure the transient key for meta transients gets generated correctly 115 | */ 116 | public function testMetaTransientKey() { 117 | 118 | $method = $this->reflect_method( 'cache_key' ); 119 | 120 | $transient_name = 'testMetaTransientKey'; 121 | $this->register_transient( $transient_name, [ 'cache_type' => 'post_meta' ] ); 122 | $this->register_transient( $transient_name . 'Hashed', [ 'cache_type' => 'post_meta', 'hash_key' => true ] ); 123 | 124 | $transient_obj = new DFM_Transients( $transient_name, 1 ); 125 | $this->assertEquals( 'dfm_transient_' . $transient_name, $method->invoke( $transient_obj ) ); 126 | 127 | $transient_obj_hashed = new DFM_Transients( $transient_name . 'Hashed', 1 ); 128 | $this->assertEquals( 'dfm_transient_' . md5( $transient_name . 'Hashed' ), $method->invoke( $transient_obj_hashed ) ); 129 | 130 | } 131 | 132 | /** 133 | * Tests to make sure key hashing is working correctly 134 | */ 135 | public function testShouldHashKey() { 136 | 137 | $should_hash_method = $this->reflect_method( 'should_hash' ); 138 | $get_key_method = $this->reflect_method( 'cache_key' ); 139 | 140 | $transient_name = 'TestTransientHashKey'; 141 | $this->register_transient( $transient_name, [ 'hash_key' => true ] ); 142 | 143 | $transient_obj = new DFM_Transients( $transient_name, '' ); 144 | 145 | $should_hash_actual = $should_hash_method->invoke( $transient_obj ); 146 | 147 | $this->assertTrue( $should_hash_actual ); 148 | $this->assertEquals( md5( $transient_name ), $get_key_method->invoke( $transient_obj ) ); 149 | 150 | $transient_obj_modifier = new DFM_Transients( $transient_name, 'test_modifier' ); 151 | $this->assertEquals( md5( $transient_name . '_test_modifier' ), $get_key_method->invoke( $transient_obj_modifier ) ); 152 | 153 | } 154 | 155 | /** 156 | * Tests to make sure key hashing is not happening outside of the scope it is supposed to 157 | */ 158 | public function testShouldNotHashKey() { 159 | 160 | $should_hash_method = $this->reflect_method( 'should_hash' ); 161 | $get_key_method = $this->reflect_method( 'cache_key' ); 162 | 163 | $transient_name = 'TestTransientNotHashKey'; 164 | $this->register_transient( $transient_name, [ 'hash_key' => false ] ); 165 | 166 | $transient_obj = new DFM_Transients( $transient_name, '' ); 167 | 168 | $should_hash_actual = $should_hash_method->invoke( $transient_obj ); 169 | 170 | $this->assertFalse( $should_hash_actual ); 171 | $this->assertEquals( $transient_name, $get_key_method->invoke( $transient_obj ) ); 172 | 173 | $transient_obj_modifier = new DFM_Transients( $transient_name, 'test_modifier' ); 174 | $this->assertEquals( $transient_name . '_test_modifier', $get_key_method->invoke( $transient_obj_modifier ) ); 175 | 176 | } 177 | 178 | /** 179 | * Tests to make sure the should_expire method is working properly 180 | */ 181 | public function testShouldExpire() { 182 | 183 | $method = $this->reflect_method( 'should_expire' ); 184 | 185 | $transient_name = 'TestTransientExpiration'; 186 | $this->register_transient( $transient_name, [ 'expiration' => HOUR_IN_SECONDS ] ); 187 | 188 | $transient_obj = new DFM_Transients( $transient_name, '' ); 189 | 190 | $actual = $method->invoke( $transient_obj ); 191 | 192 | $this->assertTrue( $actual ); 193 | 194 | } 195 | 196 | /** 197 | * Tests to make sure the should_expire method is not overreaching it's intended scope 198 | */ 199 | public function testShouldNotExpire() { 200 | 201 | $method = $this->reflect_method( 'should_expire' ); 202 | 203 | $transient_name = 'TestTransientNotExpiration'; 204 | $this->register_transient( $transient_name, [ 'expiration' => false ] ); 205 | 206 | $transient_obj = new DFM_Transients( $transient_name, '' ); 207 | 208 | $actual = $method->invoke( $transient_obj ); 209 | 210 | $this->assertFalse( $actual ); 211 | 212 | } 213 | 214 | /** 215 | * Tests to make sure the should_soft_expire method is working as intended 216 | */ 217 | public function testShouldSoftExpire() { 218 | 219 | $method = $this->reflect_method( 'should_soft_expire' ); 220 | 221 | $transient_name = 'TestSoftExpireTransient'; 222 | $this->register_transient( $transient_name, [ 'soft_expiration' => true ] ); 223 | 224 | $transient_obj = new DFM_Transients( $transient_name, '' ); 225 | 226 | $actual = $method->invoke( $transient_obj ); 227 | 228 | $this->assertTrue( $actual ); 229 | 230 | } 231 | 232 | /** 233 | * Tests to make sure the should_soft_expire method is not overreaching it's intended scope 234 | */ 235 | public function testShouldNotSoftExpire() { 236 | 237 | $method = $this->reflect_method( 'should_soft_expire' ); 238 | 239 | $transient_name = 'TestNotSoftExpireTransient'; 240 | $this->register_transient( $transient_name, [ 'soft_expiration' => false ] ); 241 | 242 | $transient_obj = new DFM_Transients( $transient_name, '' ); 243 | 244 | $actual = $method->invoke( $transient_obj ); 245 | 246 | $this->assertFalse( $actual ); 247 | 248 | } 249 | 250 | /** 251 | * Test to make sure an actual expired transient registers as expired with the is_expired method 252 | */ 253 | public function testTransientIsExpired() { 254 | 255 | $method = $this->reflect_method( 'is_expired' ); 256 | 257 | $transient_name = 'TestExpiredTransient'; 258 | $this->register_transient( $transient_name, [ 'expiration' => 1 ] ); 259 | 260 | $transient_obj = new DFM_Transients( $transient_name, '' ); 261 | 262 | $actual = $method->invoke( $transient_obj, [ 'expiration' => time() - 1, 'data' => 'test' ] ); 263 | 264 | $this->assertTrue( $actual ); 265 | 266 | } 267 | 268 | /** 269 | * Test to make sure an actual non-expired transient does not register as expired in the 270 | * is_expired method 271 | */ 272 | public function testTransientNotExpired() { 273 | 274 | $method = $this->reflect_method( 'is_expired' ); 275 | 276 | $transient_name = 'TestNotExpiredTransient'; 277 | $this->register_transient( $transient_name, [ 'expiration' => DAY_IN_SECONDS ] ); 278 | 279 | $transient_obj = new DFM_Transients( $transient_name, '' ); 280 | 281 | $actual = $method->invoke( $transient_obj, [ 'expiration' => time(), 'data' => 'test' ] ); 282 | 283 | $this->assertFalse( $actual ); 284 | 285 | } 286 | 287 | /** 288 | * Test to retrieve a transient that has a modifier 289 | */ 290 | public function testGetTransientWithModifier() { 291 | 292 | $transient_name = 'testGetTransientWithModifier'; 293 | $this->register_transient( $transient_name, [ 294 | 'callback' => function( $modifier ) { 295 | return $modifier . ' test value'; 296 | }, 297 | 'cache_type' => 'transient', 298 | ] ); 299 | 300 | $transient_obj = new DFM_Transients( $transient_name, 'something' ); 301 | $key = $transient_obj->key; 302 | 303 | $actual = $transient_obj->get(); 304 | $expected = 'something test value'; 305 | 306 | $this->assertEquals( $actual, $expected ); 307 | 308 | $transient_obj->set( 'another test' ); 309 | $this->assertEquals( 'another test', $transient_obj->get() ); 310 | 311 | $transient_obj->delete(); 312 | $this->assertFalse( get_transient( $key ) ); 313 | $this->assertEquals( $expected, $transient_obj->get() ); 314 | 315 | } 316 | 317 | /** 318 | * Test to make sure retrieving a transient with a soft expiration works correctly 319 | */ 320 | public function testGetTransientWithSoftExpire() { 321 | 322 | $transient_name = 'testGetTransientWithSoftExpire'; 323 | $this->register_transient( $transient_name, [ 324 | 'soft_expiration' => true, 325 | 'expiration' => DAY_IN_SECONDS, 326 | 'callback' => function() { 327 | return 'some string'; 328 | }, 329 | ] ); 330 | 331 | $transient_obj = new DFM_Transients( $transient_name, '' ); 332 | $key = $transient_obj->key; 333 | 334 | $expected = 'some string'; 335 | $actual = $transient_obj->get(); 336 | $this->assertEquals( $expected, $actual ); 337 | $this->assertArrayHasKey( 'data', get_transient( $key ) ); 338 | $this->assertArrayHasKey( 'expiration', get_transient( $key ) ); 339 | 340 | $transient_obj->set( 'some other string' ); 341 | $this->assertEquals( 'some other string', $transient_obj->get() ); 342 | 343 | $transient_obj->delete(); 344 | $this->assertEquals( $expected, $transient_obj->get() ); 345 | 346 | } 347 | 348 | /** 349 | * Test to make sure getting transient data from term meta works correctly 350 | */ 351 | public function testGetTransientFromTermMeta() { 352 | 353 | $term_arr = wp_insert_term( 'Test Term', 'category' ); 354 | $term_id = (int) $term_arr['term_id']; 355 | 356 | $transient_name = 'testGetTransientFromTermMeta'; 357 | $this->register_transient( $transient_name, [ 358 | 'callback' => function( $modifier, $term_id ) { 359 | return 'term id: ' . $term_id; 360 | }, 361 | 'cache_type' => 'term_meta', 362 | ] ); 363 | 364 | $transient_obj = new DFM_Transients( $transient_name, '', $term_id ); 365 | $key = $transient_obj->key; 366 | 367 | $actual = $transient_obj->get(); 368 | $expected = 'term id: ' . $term_id; 369 | 370 | $this->assertEquals( $expected, $actual ); 371 | 372 | $transient_obj->set( 'some test value' ); 373 | $this->assertEquals( 'some test value', $transient_obj->get() ); 374 | 375 | $transient_obj->delete(); 376 | $this->assertEquals( '', get_term_meta( $term_id, $key, true ) ); 377 | $this->assertEquals( $expected, $transient_obj->get() ); 378 | $this->assertEquals( $expected, get_term_meta( $term_id, $key, true ) ); 379 | 380 | } 381 | 382 | /** 383 | * Test getting a transient from term meta when there are multiple modifiers 384 | * @throws Exception 385 | */ 386 | public function testGetTransientFromTermMetaWithModifier() { 387 | 388 | $term_id = $this->factory->term->create(); 389 | 390 | $transient_name = 'testGetTransientFromTermMetaWithModifier'; 391 | $return_string = 'Term ID: %d, Modifier: %s'; 392 | $test_value = 'some test value'; 393 | 394 | $this->register_transient( $transient_name, [ 395 | 'callback' => function( $modifier, $term_id ) use ( $return_string ) { 396 | return sprintf( $return_string, $term_id, $modifier ); 397 | }, 398 | 'cache_type' => 'term_meta', 399 | 'hash_key' => true, 400 | ] ); 401 | 402 | $modifier_1 = 'first_mod'; 403 | $modifier_2 = 'second_mod'; 404 | 405 | $transient_obj_1 = new DFM_Transients( $transient_name, $modifier_1, $term_id ); 406 | $transient_obj_2 = new DFM_Transients( $transient_name, $modifier_2, $term_id ); 407 | $key_1 = $transient_obj_1->key; 408 | $key_2 = $transient_obj_2->key; 409 | 410 | $this->assertEquals( sprintf( $return_string, $term_id, $modifier_1 ), $transient_obj_1->get() ); 411 | $this->assertEquals( sprintf( $return_string, $term_id, $modifier_2 ), $transient_obj_2->get() ); 412 | 413 | $transient_obj_1->set( $test_value ); 414 | $transient_obj_2->set( $test_value ); 415 | $this->assertEquals( $test_value, $transient_obj_1->get() ); 416 | $this->assertEquals( $test_value, $transient_obj_2->get() ); 417 | 418 | $transient_obj_1->delete(); 419 | $this->assertEquals( '', get_term_meta( $term_id, $key_1, true ) ); 420 | $this->assertEquals( sprintf( $return_string, $term_id, $modifier_1 ), $transient_obj_1->get() ); 421 | $this->assertEquals( sprintf( $return_string, $term_id, $modifier_1 ), get_term_meta( $term_id, $key_1, true ) ); 422 | 423 | $transient_obj_2->delete(); 424 | $this->assertEquals( '', get_term_meta( $term_id, $key_2, true ) ); 425 | $this->assertEquals( sprintf( $return_string, $term_id, $modifier_2 ), $transient_obj_2->get() ); 426 | $this->assertEquals( sprintf( $return_string, $term_id, $modifier_2 ), get_term_meta( $term_id, $key_2, true ) ); 427 | 428 | } 429 | 430 | /** 431 | * Test for retrieving transients with a modifier using the post meta cache 432 | */ 433 | public function testGetTransientFromPostMetaWithModifier() { 434 | 435 | $post_id = $this->factory->post->create(); 436 | 437 | $transient_name = 'testGetTransientFromPostMetaWithModifier'; 438 | $return_string = 'Post ID: %d, Modifier: %s'; 439 | $test_value = 'test value'; 440 | 441 | $this->register_transient( $transient_name, [ 442 | 'callback' => function( $modifier, $post_id ) use ( $return_string ) { 443 | return sprintf( $return_string, $post_id, $modifier ); 444 | }, 445 | 'cache_type' => 'post_meta', 446 | 'async_updates' => true, 447 | 'update_hooks' => 'updated_post_meta', 448 | 'expiration' => HOUR_IN_SECONDS, 449 | 'soft_expiration' => true, 450 | ] ); 451 | 452 | $modifier_1 = 'dragons'; 453 | $modifier_2 = 'rainbows'; 454 | 455 | $transient_obj_1 = new DFM_Transients( $transient_name, $modifier_1, $post_id ); 456 | $transient_obj_2 = new DFM_Transients( $transient_name, $modifier_2, $post_id ); 457 | $key_1 = $transient_obj_1->key; 458 | $key_2 = $transient_obj_2->key; 459 | 460 | $this->assertEquals( sprintf( $return_string, $post_id, $modifier_1 ), $transient_obj_1->get() ); 461 | $this->assertEquals( sprintf( $return_string, $post_id, $modifier_2 ), $transient_obj_2->get() ); 462 | 463 | $transient_obj_1->set( $test_value ); 464 | $transient_obj_2->set( $test_value ); 465 | $this->assertEquals( $test_value, $transient_obj_1->get() ); 466 | $this->assertEquals( $test_value, $transient_obj_2->get() ); 467 | 468 | $transient_obj_1->delete(); 469 | $this->assertEquals( '', get_post_meta( $post_id, $key_1, true) ); 470 | $this->assertEquals( sprintf( $return_string, $post_id, $modifier_1 ), $transient_obj_1->get() ); 471 | $this->assertEquals( sprintf( $return_string, $post_id, $modifier_1 ), get_post_meta( $post_id, $key_1, true )['data'] ); 472 | $this->assertArrayHasKey( 'expiration', get_post_meta( $post_id, $key_1, true ) ); 473 | 474 | $transient_obj_2->delete(); 475 | $this->assertEquals( '', get_post_meta( $post_id, $key_2, true) ); 476 | $this->assertEquals( sprintf( $return_string, $post_id, $modifier_2 ), $transient_obj_2->get() ); 477 | $this->assertEquals( sprintf( $return_string, $post_id, $modifier_2 ), get_post_meta( $post_id, $key_2, true )['data'] ); 478 | $this->assertArrayHasKey( 'expiration', get_post_meta( $post_id, $key_2, true ) ); 479 | 480 | } 481 | 482 | /** 483 | * Tests to ensure we haven't broken object type cache transients before we implemented modifiers for them 484 | */ 485 | public function testTermMetaTransientBackwardsCompat() { 486 | 487 | $term_id = $this->factory->term->create(); 488 | $transient_name = 'testTermMetaTransientBackwardsCompat'; 489 | $return_string = 'term id: %d'; 490 | 491 | $this->register_transient( $transient_name, [ 492 | 'callback' => function( $modifier ) use ( $return_string ) { 493 | return sprintf( $return_string, $modifier ); 494 | }, 495 | 'cache_type' => 'term_meta', 496 | ] ); 497 | 498 | $transient_obj = new DFM_Transients( $transient_name, $term_id ); 499 | $key = $transient_obj->key; 500 | 501 | $this->assertEquals( sprintf( $return_string, $term_id ), $transient_obj->get() ); 502 | 503 | $transient_obj->set( 'test data' ); 504 | $this->assertEquals( 'test data', $transient_obj->get() ); 505 | 506 | $transient_obj->delete(); 507 | $this->assertEquals( '', get_term_meta( $term_id, $key, true ) ); 508 | $this->assertEquals( sprintf( $return_string, $term_id ), $transient_obj->get() ); 509 | $this->assertEquals( sprintf( $return_string, $term_id ), get_term_meta( $term_id, $key, true ) ); 510 | 511 | } 512 | 513 | /** 514 | * Test to make sure getting transient data from post meta works correctly 515 | */ 516 | public function testGetTransientFromPostMeta() { 517 | 518 | $post_id = $this->factory->post->create(); 519 | $transient_name = 'testGetTransientFromPostMeta'; 520 | $custom_modifier = 'test'; 521 | 522 | $this->register_transient( $transient_name, [ 523 | 'callback' => function( $modifier ) { 524 | return 'transient data: ' . $modifier; 525 | }, 526 | 'cache_type' => 'post_meta', 527 | ] ); 528 | 529 | $transient_obj = new DFM_Transients( $transient_name, $custom_modifier, $post_id ); 530 | $key = $transient_obj->key; 531 | 532 | $actual = $transient_obj->get(); 533 | $expected = 'transient data: ' . $custom_modifier; 534 | $this->assertEquals( $expected, $actual ); 535 | 536 | $transient_obj->set( 'some test value' ); 537 | $this->assertEquals( 'some test value', $transient_obj->get() ); 538 | 539 | $transient_obj->delete(); 540 | $this->assertEquals( '', get_post_meta( $post_id, $key, true ) ); 541 | $this->assertEquals( $expected, $transient_obj->get() ); 542 | $this->assertEquals( $expected, get_post_meta( $post_id, $key, true ) ); 543 | 544 | } 545 | 546 | /** 547 | * Test to make sure getting transient data from meta with an expiration works correctly 548 | */ 549 | public function testGetTransientFromMetaWithExpiration() { 550 | 551 | $post_id = $this->factory->post->create(); 552 | $transient_name = 'testGetTransientFromMetaWithExpiration'; 553 | 554 | $this->register_transient( $transient_name, [ 555 | 'callback' => function() { 556 | return 'transient data for post'; 557 | }, 558 | 'cache_type' => 'post_meta', 559 | 'expiration' => DAY_IN_SECONDS, 560 | 'soft_expiration' => true, 561 | 'hash_key' => true, 562 | ] ); 563 | 564 | $transient_obj = new DFM_Transients( $transient_name, '', $post_id ); 565 | $key = $transient_obj->key; 566 | 567 | $actual = $transient_obj->get(); 568 | $expected = 'transient data for post'; 569 | $this->assertEquals( $expected, $actual ); 570 | 571 | $transient_obj->set( 'some test value' ); 572 | $this->assertEquals( 'some test value', $transient_obj->get() ); 573 | $this->assertArrayHasKey( 'data', get_post_meta( $post_id, $key, true ) ); 574 | $this->assertArrayHasKey( 'expiration', get_post_meta( $post_id, $key, true ) ); 575 | 576 | $transient_obj->delete(); 577 | $this->assertEquals( '', get_post_meta( $post_id, $key, true ) ); 578 | $this->assertEquals( $expected, $transient_obj->get() ); 579 | $this->assertEquals( $expected, get_post_meta( $post_id, $key, true )['data'] ); 580 | 581 | } 582 | 583 | /** 584 | * Test to make sure getting transient data from user meta works correctly 585 | */ 586 | public function testGetTransientFromUserMeta() { 587 | 588 | $user_id = $this->factory->user->create(); 589 | $transient_name = 'testGetTransientFromUserMeta'; 590 | $return_string = 'User ID: %d, Modifier: %s'; 591 | 592 | $this->register_transient( $transient_name, [ 593 | 'callback' => function( $modifier, $user_ID ) use ( $return_string ) { 594 | return sprintf( $return_string, $user_ID, $modifier ); 595 | }, 596 | 'cache_type' => 'user_meta', 597 | ] ); 598 | 599 | $transient_obj = new DFM_Transients( $transient_name, '', $user_id ); 600 | $key = $transient_obj->key; 601 | 602 | $this->assertEquals( sprintf( $return_string, $user_id, '' ), $transient_obj->get() ); 603 | 604 | $transient_obj->set( 'some test value' ); 605 | $this->assertEquals( 'some test value', $transient_obj->get() ); 606 | 607 | $transient_obj->delete(); 608 | $this->assertEquals( '', get_user_meta( $user_id, $key, true ) ); 609 | $this->assertEquals( sprintf( $return_string, $user_id, '' ), $transient_obj->get() ); 610 | $this->assertEquals( sprintf( $return_string, $user_id, '' ), get_user_meta( $user_id, $key, true ) ); 611 | 612 | } 613 | 614 | /** 615 | * Test for retrieving a transient while the code is doing a retry 616 | */ 617 | public function testGetTransientDataDuringRetry() { 618 | 619 | $transient_name = 'testGetTransientDataDuringRetry'; 620 | $this->register_transient( $transient_name ); 621 | 622 | $transient_obj = $this->getMockBuilder( 'DFM_Transients' ) 623 | ->setConstructorArgs( [ $transient_name, '' ] ) 624 | ->setMethods( [ 'doing_retry' ] ) 625 | ->getMock(); 626 | 627 | $transient_obj->expects( $this->any() ) 628 | ->method( 'doing_retry' ) 629 | ->will( $this->returnValue( true ) ); 630 | 631 | $actual = $transient_obj->get(); 632 | $this->assertFalse( $actual ); 633 | 634 | } 635 | 636 | /** 637 | * Test to make sure getting transient data from a transient cache with a soft expiration 638 | * works correctly 639 | */ 640 | public function testUpdateForSoftExpiredData() { 641 | 642 | $transient_name = 'testUpdateForSoftExpiredData'; 643 | $this->register_transient( $transient_name, [ 644 | 'soft_expiration' => true, 645 | 'expiration' => HOUR_IN_SECONDS, 646 | 'callback' => function() { 647 | return 'test data'; 648 | } 649 | ] ); 650 | 651 | $transient_obj = $this->getMockBuilder( 'DFM_Transients' ) 652 | ->setConstructorArgs( [ $transient_name, '' ] ) 653 | ->setMethods( [ 'is_expired' ] ) 654 | ->getMock(); 655 | 656 | $transient_obj->expects( $this->any() ) 657 | ->method( 'is_expired' ) 658 | ->will( $this->returnValue( true ) ); 659 | 660 | $expected = 'some string'; 661 | $transient_obj->set( $expected ); 662 | $actual = $transient_obj->get(); 663 | $this->assertEquals( $expected, $actual ); 664 | 665 | /** 666 | * Removing this core hook so we can better tell if our async handler is actually getting 667 | * added to the shutdown hook 668 | */ 669 | remove_action( 'shutdown', 'wp_ob_end_flush_all', 1 ); 670 | $this->assertTrue( has_filter( 'shutdown' ) ); 671 | 672 | } 673 | 674 | /** 675 | * Tests to make sure data with a hard expiration gets regenerated properly 676 | */ 677 | public function testUpdateForHardExpiredData() { 678 | 679 | $transient_name = 'testUpdateForHardExpiredData'; 680 | $this->register_transient( $transient_name, [ 681 | 'expiration' => HOUR_IN_SECONDS, 682 | 'callback' => function() { 683 | return 'test data'; 684 | } 685 | ] ); 686 | 687 | $transient_obj = $this->getMockBuilder( 'DFM_Transients' ) 688 | ->setConstructorArgs( [ $transient_name, '' ] ) 689 | ->setMethods( [ 'is_expired' ] ) 690 | ->getMock(); 691 | 692 | $transient_obj->expects( $this->any() ) 693 | ->method( 'is_expired' ) 694 | ->will( $this->returnValue( true ) ); 695 | 696 | $expected = 'test data'; 697 | $transient_obj->set( 'some other data' ); 698 | $actual = $transient_obj->get(); 699 | 700 | $this->assertEquals( $expected, $actual ); 701 | 702 | } 703 | 704 | /** 705 | * Tests to make sure if we try to get data from a transient that was registered with a 706 | * non-existent type it throws an error 707 | */ 708 | public function testGetTransientFromNonExistentType() { 709 | 710 | $transient_name = 'testGetTransientFromNonExistentType'; 711 | $this->register_transient( $transient_name, [ 'cache_type' => 'none' ] ); 712 | 713 | $transient_obj = new DFM_Transients( $transient_name, '' ); 714 | 715 | $this->assertTrue( is_wp_error( $transient_obj->get() ) ); 716 | $this->assertTrue( is_wp_error( $transient_obj->set( 'test' ) ) ); 717 | 718 | } 719 | 720 | /** 721 | * Tests to make sure if we try to get delete data from a transient that was registered with a 722 | * non-existent type it throws an error 723 | */ 724 | public function testDeleteTransientFromNonExistentType() { 725 | 726 | $transient_name = 'testDeleteTransientFromNonExistentType'; 727 | $this->register_transient( $transient_name, [ 'cache_type' => 'none' ] ); 728 | 729 | $transient_obj = new DFM_Transients( $transient_name, '' ); 730 | 731 | $this->assertTrue( is_wp_error( $transient_obj->delete() ) ); 732 | 733 | } 734 | 735 | /** 736 | * Helper method to reflect a method within a class 737 | * 738 | * @param string $method_name Name of the method you want to reflect 739 | * @return ReflectionMethod 740 | */ 741 | private function reflect_method( $method_name ) { 742 | 743 | $method = new ReflectionMethod( 'DFM_Transients', $method_name ); 744 | $method->setAccessible( true ); 745 | 746 | return $method; 747 | 748 | } 749 | 750 | /** 751 | * Helper function to register a transient 752 | * 753 | * @param string $name Name of the transient to register 754 | * @param array $args Arguments to register for the transient 755 | */ 756 | private function register_transient( $name, $args = [] ) { 757 | 758 | $defaults = [ 759 | 'callback' => '__return_false', 760 | ]; 761 | 762 | dfm_register_transient( $name, wp_parse_args( $args, $defaults ) ); 763 | 764 | } 765 | 766 | } 767 | -------------------------------------------------------------------------------- /tests/test-template-tags.php: -------------------------------------------------------------------------------- 1 | assertEquals( (object) $expected_args, $dfm_transients[ $transient_name ] ); 20 | 21 | } 22 | 23 | /** 24 | * Test to make sure an error gets thrown when you try to register a transient without a name 25 | */ 26 | public function testUnsuccessfulRegistration() { 27 | 28 | $transient_name = 'testTransientWithoutCallback'; 29 | 30 | $this->setExpectedException( 'Exception', 'You must add a callback when registering a transient' ); 31 | $this->expectException( dfm_register_transient( $transient_name, [] ) ); 32 | 33 | } 34 | 35 | /** 36 | * Simple get transient test 37 | */ 38 | public function testGetTransient() { 39 | 40 | $transient_name = 'testTransientGet'; 41 | $transient_value = 'test value'; 42 | $r = set_transient( $transient_name, $transient_value ); 43 | 44 | $this->assertTrue( $r ); 45 | 46 | dfm_register_transient( $transient_name, [ 47 | 'cache_type' => 'transient', 48 | 'callback' => function() { 49 | return 'test value'; 50 | }, 51 | ] ); 52 | 53 | $actual = dfm_get_transient( $transient_name ); 54 | 55 | $this->assertEquals( $transient_value, $actual ); 56 | 57 | dfm_delete_transient( $transient_name ); 58 | 59 | $this->assertFalse( get_transient( $transient_name ) ); 60 | 61 | } 62 | 63 | /** 64 | * Test to get a transient that has no initial value 65 | */ 66 | public function testGetTransientWithNoInitialValue() { 67 | 68 | $transient_name = 'testTransientGetWithoutValue'; 69 | $expected = 'test value'; 70 | 71 | dfm_register_transient( $transient_name, [ 72 | 'cache_type' => 'transient', 73 | 'callback' => function() { 74 | return 'test value'; 75 | }, 76 | ] ); 77 | 78 | $actual = dfm_get_transient( $transient_name ); 79 | 80 | $this->assertEquals( $expected, $actual ); 81 | 82 | dfm_delete_transient( $transient_name ); 83 | $this->assertFalse( get_transient( $transient_name ) ); 84 | 85 | } 86 | 87 | /** 88 | * Test to get a transient from post meta 89 | */ 90 | public function testGetTransientPostMeta() { 91 | 92 | $transient_name = 'testTransientGetFromPostMeta'; 93 | $expected = 'test value'; 94 | 95 | $post_id = $this->factory->post->create(); 96 | 97 | update_post_meta( $post_id, 'dfm_transient_' . $transient_name, $expected ); 98 | 99 | dfm_register_transient( $transient_name, [ 'cache_type' => 'post_meta', 'callback' => '__return_true' ] ); 100 | $actual = dfm_get_transient( $transient_name, $post_id ); 101 | 102 | $this->assertEquals( $expected, $actual ); 103 | 104 | } 105 | 106 | /** 107 | * Helper method for the register test data provider 108 | * 109 | * @return array 110 | */ 111 | public function providerRegisterTest() { 112 | return [ 113 | [ 114 | 'testTransient1', 115 | [ 116 | 'hash_key' => false, 117 | 'cache_type' => 'transient', 118 | 'callback' => '__return_true', 119 | 'async_updates' => false, 120 | 'update_hooks' => [], 121 | 'expiration' => false, 122 | 'soft_expiration' => false, 123 | ], 124 | [ 125 | 'key' => 'testTransient1', 126 | 'hash_key' => false, 127 | 'cache_type' => 'transient', 128 | 'callback' => '__return_true', 129 | 'async_updates' => false, 130 | 'update_hooks' => [], 131 | 'expiration' => false, 132 | 'soft_expiration' => false, 133 | ], 134 | ], 135 | [ 136 | 'testTransient2', 137 | [ 138 | 'key' => 'testTransient2Key', 139 | 'hash_key' => false, 140 | 'cache_type' => 'term_meta', 141 | 'callback' => '__return_true', 142 | 'async_updates' => true, 143 | 'update_hooks' => [ 'update_post' ], 144 | 'expiration' => HOUR_IN_SECONDS, 145 | 'soft_expiration' => true, 146 | ], 147 | [ 148 | 'key' => 'testTransient2Key', 149 | 'hash_key' => false, 150 | 'cache_type' => 'term_meta', 151 | 'callback' => '__return_true', 152 | 'async_updates' => true, 153 | 'update_hooks' => [ 'update_post' ], 154 | 'expiration' => HOUR_IN_SECONDS, 155 | 'soft_expiration' => true, 156 | ], 157 | ], 158 | [ 159 | 'testTransient3', 160 | [ 161 | 'callback' => '__return_true', 162 | ], 163 | [ 164 | 'key' => 'testTransient3', 165 | 'hash_key' => false, 166 | 'cache_type' => 'transient', 167 | 'callback' => '__return_true', 168 | 'async_updates' => false, 169 | 'update_hooks' => false, 170 | 'expiration' => false, 171 | 'soft_expiration' => false, 172 | ], 173 | ], 174 | ]; 175 | } 176 | 177 | } 178 | --------------------------------------------------------------------------------