From 18f4aba1469922745ec633411000a0580eefdb97 Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Wed, 21 Feb 2018 14:07:00 +0100 Subject: [PATCH] added redis contrib module --- .../all/modules/contrib/dev/redis/.gitignore | 1 + .../all/modules/contrib/dev/redis/LICENSE.txt | 339 +++++++++ .../contrib/dev/redis/README.PhpRedis.txt | 37 + .../contrib/dev/redis/README.Predis.txt | 60 ++ .../contrib/dev/redis/README.testing.txt | 40 ++ .../all/modules/contrib/dev/redis/README.txt | 474 +++++++++++++ .../dev/redis/lib/Redis/AbstractBackend.php | 108 +++ .../dev/redis/lib/Redis/BackendInterface.php | 59 ++ .../contrib/dev/redis/lib/Redis/Cache.php | 655 ++++++++++++++++++ .../lib/Redis/Cache/BackendInterface.php | 102 +++ .../dev/redis/lib/Redis/Cache/Base.php | 39 ++ .../dev/redis/lib/Redis/Cache/PhpRedis.php | 149 ++++ .../dev/redis/lib/Redis/Cache/Predis.php | 145 ++++ .../dev/redis/lib/Redis/CacheCompressed.php | 66 ++ .../contrib/dev/redis/lib/Redis/Client.php | 241 +++++++ .../lib/Redis/Client/FactoryInterface.php | 32 + .../dev/redis/lib/Redis/Client/Manager.php | 144 ++++ .../dev/redis/lib/Redis/Client/PhpRedis.php | 36 + .../dev/redis/lib/Redis/Client/Predis.php | 145 ++++ .../contrib/dev/redis/lib/Redis/Lock.php | 31 + .../redis/lib/Redis/Lock/BackendInterface.php | 61 ++ .../redis/lib/Redis/Lock/DefaultBackend.php | 89 +++ .../dev/redis/lib/Redis/Lock/PhpRedis.php | 138 ++++ .../dev/redis/lib/Redis/Lock/Predis.php | 137 ++++ .../lib/Redis/Path/AbstractHashLookup.php | 105 +++ .../lib/Redis/Path/HashLookupInterface.php | 109 +++ .../redis/lib/Redis/Path/NullHashLookup.php | 27 + .../dev/redis/lib/Redis/Path/PhpRedis.php | 108 +++ .../dev/redis/lib/Redis/Path/Predis.php | 108 +++ .../contrib/dev/redis/lib/Redis/Queue.php | 58 ++ .../dev/redis/lib/Redis/Queue/Base.php | 99 +++ .../dev/redis/lib/Redis/Queue/PhpRedis.php | 106 +++ .../lib/Redis/Tests/AbstractUnitTestCase.php | 141 ++++ .../Redis/Tests/Admin/VariableTestCase.test | 60 ++ .../CompressedPhpRedisFixesUnitTestCase.test | 27 + .../CompressedPhpRedisFlushUnitTestCase.test | 27 + ...essedPhpRedisShardedFixesUnitTestCase.test | 25 + ...essedPhpRedisShardedFlushUnitTestCase.test | 25 + ...sShardedWithPipelineFixesUnitTestCase.test | 25 + .../Redis/Tests/Cache/FixesUnitTestCase.php | 209 ++++++ .../Redis/Tests/Cache/FlushUnitTestCase.php | 185 +++++ .../Cache/PhpRedisFixesUnitTestCase.test | 22 + .../Cache/PhpRedisFlushUnitTestCase.test | 22 + .../PhpRedisShardedFixesUnitTestCase.test | 20 + .../PhpRedisShardedFlushUnitTestCase.test | 20 + ...sShardedWithPipelineFixesUnitTestCase.test | 20 + .../Tests/Cache/PredisFixesUnitTestCase.test | 18 + .../Tests/Cache/PredisFlushUnitTestCase.test | 18 + .../Cache/PredisShardedFixesUnitTestCase.test | 20 + .../Cache/PredisShardedFlushUnitTestCase.test | 20 + ...sShardedWithPipelineFixesUnitTestCase.test | 20 + .../Tests/Client/ClientUnitTestCase.test | 66 ++ .../lib/Redis/Tests/Client/MockFactory.php | 14 + .../Redis/Tests/Lock/LockingUnitTestCase.php | 119 ++++ .../Lock/PhpRedisLockingUnitTestCase.test | 27 + .../Tests/Lock/PredisLockingUnitTestCase.test | 23 + .../lib/Redis/Tests/Path/PathUnitTestCase.php | 148 ++++ .../Tests/Path/PhpRedisPathUnitTestCase.test | 22 + .../Tests/Path/PredisPathUnitTestCase.test | 18 + .../Queue/PhpRedisQueueUnitTestCase.test | 22 + .../Tests/Queue/PredisQueueUnitTestCase.test | 20 + .../Redis/Tests/Queue/QueueUnitTestCase.php | 133 ++++ .../modules/contrib/dev/redis/redis.admin.inc | 104 +++ .../contrib/dev/redis/redis.autoload.inc | 25 + .../all/modules/contrib/dev/redis/redis.info | 44 ++ .../modules/contrib/dev/redis/redis.install | 38 + .../modules/contrib/dev/redis/redis.lock.inc | 63 ++ .../modules/contrib/dev/redis/redis.module | 73 ++ .../modules/contrib/dev/redis/redis.path.inc | 594 ++++++++++++++++ 69 files changed, 6525 insertions(+) create mode 100644 sites/all/modules/contrib/dev/redis/.gitignore create mode 100644 sites/all/modules/contrib/dev/redis/LICENSE.txt create mode 100644 sites/all/modules/contrib/dev/redis/README.PhpRedis.txt create mode 100644 sites/all/modules/contrib/dev/redis/README.Predis.txt create mode 100644 sites/all/modules/contrib/dev/redis/README.testing.txt create mode 100644 sites/all/modules/contrib/dev/redis/README.txt create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/AbstractBackend.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/BackendInterface.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Cache.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Cache/BackendInterface.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Base.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Cache/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/CacheCompressed.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Client.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Client/FactoryInterface.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Client/Manager.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Client/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Lock/BackendInterface.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Lock/DefaultBackend.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Path/AbstractHashLookup.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Path/HashLookupInterface.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Path/NullHashLookup.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Path/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/AbstractUnitTestCase.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Admin/VariableTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFlushUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFlushUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedWithPipelineFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FixesUnitTestCase.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FlushUnitTestCase.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFlushUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFlushUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedWithPipelineFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFlushUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFlushUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedWithPipelineFixesUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/ClientUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/MockFactory.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/LockingUnitTestCase.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PhpRedisLockingUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PredisLockingUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PathUnitTestCase.php create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PhpRedisPathUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PredisPathUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PhpRedisQueueUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PredisQueueUnitTestCase.test create mode 100644 sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/QueueUnitTestCase.php create mode 100644 sites/all/modules/contrib/dev/redis/redis.admin.inc create mode 100644 sites/all/modules/contrib/dev/redis/redis.autoload.inc create mode 100644 sites/all/modules/contrib/dev/redis/redis.info create mode 100644 sites/all/modules/contrib/dev/redis/redis.install create mode 100644 sites/all/modules/contrib/dev/redis/redis.lock.inc create mode 100644 sites/all/modules/contrib/dev/redis/redis.module create mode 100644 sites/all/modules/contrib/dev/redis/redis.path.inc diff --git a/sites/all/modules/contrib/dev/redis/.gitignore b/sites/all/modules/contrib/dev/redis/.gitignore new file mode 100644 index 00000000..a48738a7 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/.gitignore @@ -0,0 +1 @@ +predis diff --git a/sites/all/modules/contrib/dev/redis/LICENSE.txt b/sites/all/modules/contrib/dev/redis/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/dev/redis/README.PhpRedis.txt b/sites/all/modules/contrib/dev/redis/README.PhpRedis.txt new file mode 100644 index 00000000..aa1146de --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/README.PhpRedis.txt @@ -0,0 +1,37 @@ +PhpRedis cache backend +====================== + +This client, for now, is only able to use the PhpRedis extension. + +Get PhpRedis +------------ + +You can download this library at: + + https://github.com/phpredis/phpredis + +Most common Linux distribution should now have packages for this PHP extension +however if that's not the case for the one you use, use the above link to find +the release source download links and follow the provided instructions in order +to compile it for your system. + +Default behavior is to connect via tcp://localhost:6379 but you might want to +connect differently. + +Connect via UNIX socket +----------------------- + +Just add this line to your settings.php file: + + $conf['redis_cache_socket'] = '/tmp/redis.sock'; + +Don't forget to change the path depending on your operating system and Redis +server configuration. + +Connect to a remote host and database +------------------------------------- + +See README.txt file. + +For this particular implementation, host settings are overridden by the +UNIX socket parameter. diff --git a/sites/all/modules/contrib/dev/redis/README.Predis.txt b/sites/all/modules/contrib/dev/redis/README.Predis.txt new file mode 100644 index 00000000..e32c3cd3 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/README.Predis.txt @@ -0,0 +1,60 @@ +Predis cache backend +==================== + +This module will work with the Predis 1.x version. Any earlier versions +are unsupported. + +This client, for now, is only able to use the Predis PHP library. + +The Predis library requires PHP 5.3 minimum. If your hosted environment does +not ships with at least PHP 5.3, please do not use this cache backend. + +Please consider using an OPCode cache such as APC. Predis is a good and fully +featured API, the cost is that the code is a lot more than a single file in +opposition to some other backends such as the APC one. + +Get Predis +---------- + +You can download this library at: + + https://github.com/nrk/predis + +This file explains how to install the Predis library and the Drupal cache +backend. If you are an advanced Drupal integrator, please consider the fact +that you can easily change all the pathes. Pathes used in this file are +likely to be default for non advanced users. + +Download and install library +---------------------------- + +Once done, you either have to clone it into: + + sites/all/libraries/predis + +So that you have the following directory tree: + + sites/all/libraries/predis/src/ # Where the PHP code stands + +Or, any other place in order to share it: +For example, from your install profiles libraries folder: + + profiles/example/libraries/predis + +If you choose this solution, you have to alter a bit your $conf array into +the settings.php file as this: + + define('PREDIS_BASE_PATH', DRUPAL_ROOT . '/profiles/example/libraries/predis/'); + +Connect to a remote host and database +------------------------------------- + +See README.txt file. + +Advanced configuration (PHP expert) +----------------------------------- + +Best solution is, whatever is the place where you put the Predis library, that +you set up a fully working autoloader able to use it. The one being used by the +Redis module is a default fallback and will naturally being appened to the SPL +autoloader stack. diff --git a/sites/all/modules/contrib/dev/redis/README.testing.txt b/sites/all/modules/contrib/dev/redis/README.testing.txt new file mode 100644 index 00000000..5a68b0c7 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/README.testing.txt @@ -0,0 +1,40 @@ +Redis module testing +==================== + +Unit tests won't cover the cache backend until the 8.x series. + +This won't be fixed, by design. Drupal 8.x now ships a complete cache backend +unit tests suite which will be used when this module will be upgraded. + +7.x testing +=========== + +7.x testing will keep it to the bare minimum and will test some minor bugs such +as admin UI or autoloading related bugs. + +Cleanup environment +=================== + +php -f scripts/run-tests.sh -- --clean + +Run common tests +================ + + +php -f scripts/run-tests.sh -- --verbose --color \ + --url "http://yoursite" \ + --class "Redis_Tests_Client_UnitTestCase" + +Run all PhpRedis tests +====================== + +php -f scripts/run-tests.sh -- --verbose --color \ + --url "http://laborange.net" \ + --class "Redis_Tests_Cache_PhpRedisFixesUnitTestCase,Redis_Tests_Cache_PhpRedisFlushUnitTestCase,Redis_Tests_Cache_PhpRedisShardedFixesUnitTestCase,Redis_Tests_Cache_PhpRedisShardedFlushUnitTestCase,Redis_Tests_Cache_PhpRedisShardedWithPipelineFixesUnitTestCase,Redis_Tests_Lock_PhpRedisLockingUnitTestCase,Redis_Tests_Path_PhpRedisPathUnitTestCase,Redis_Tests_Queue_PhpRedisQueueUnitTestCase" + +Run all Predis tests +====================== + +php -f scripts/run-tests.sh -- --verbose --color \ + --url "http://yoursite" \ + --class "Redis_Tests_Cache_PredisFixesUnitTestCase,Redis_Tests_Cache_PredisFlushUnitTestCase,Redis_Tests_Cache_PredisShardedFixesUnitTestCase,Redis_Tests_Cache_PredisShardedFlushUnitTestCase,Redis_Tests_Cache_PredisShardedWithPipelineFixesUnitTestCase,Redis_Tests_Lock_PredisLockingUnitTestCase,Redis_Tests_Path_PredisPathUnitTestCase" diff --git a/sites/all/modules/contrib/dev/redis/README.txt b/sites/all/modules/contrib/dev/redis/README.txt new file mode 100644 index 00000000..37357eb3 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/README.txt @@ -0,0 +1,474 @@ +Redis cache backends +==================== + +This package provides two different Redis cache backends. If you want to use +Redis as cache backend, you have to choose one of the two, but you cannot use +both at the same time. Well, it will be technically possible, but it would be +quite a dumb thing to do. + +Predis +------ + +This implementation uses the Predis PHP library. It is compatible PHP 5.3 +only. + +PhpRedis +-------- + +This implementation uses the PhpRedis PHP extention. In order to use it, you +probably will need to compile the extension yourself. + +Redis version +------------- + +This module requires Redis version to be 2.6.0 or later with LUA scrpting +enabled due to the EVAL command usage. + +If you can't upgrade you Redis server: + + - 3.x release will only officially support Redis server <= 2.8 and 3.x + nevertheless you may use it with Redis 2.4 if you configure your cache + backend to operate in sharding mode. + + - For Redis 2.4 use the latest 2.x release of this module or use the + 3.x release with sharding mode enabled. + + - For Redis <=2.3 use any version of this module <=2.6 + +Notes +----- + +Both backends provide the exact same functionalities. The major difference is +because PhpRedis uses a PHP extension, and not PHP code, it will performe a +lot better (Predis needs PHP userland code to be loaded). + +Difference is not that visible, it's really a few millisec on my testing box, +in case you attempt to profile the code, traces will be a lot bigger. + +Note that most of the settings are shared. See next sections. + +Getting started +=============== + +Quick setup +----------- + +Here is a simple yet working easy way to setup the module. +This method will allow Drupal to use Redis for all caches and locks +and path alias cache replacement. + + $conf['redis_client_interface'] = 'PhpRedis'; // Can be "Predis". + $conf['redis_client_host'] = '1.2.3.4'; // Your Redis instance hostname. + $conf['lock_inc'] = 'sites/all/modules/redis/redis.lock.inc'; + $conf['path_inc'] = 'sites/all/modules/redis/redis.path.inc'; + $conf['cache_backends'][] = 'sites/all/modules/redis/redis.autoload.inc'; + $conf['cache_default_class'] = 'Redis_Cache'; + +See next chapters for more information. + +Is there any cache bins that should *never* go into Redis? +---------------------------------------------------------- + +TL;DR: No. Except for 'cache_form' if you use Redis with LRU eviction. + +Redis has been maturing a lot over time, and will apply different sensible +settings for different bins; It's today very stable. + +Advanced configuration +====================== + +Use the compressed cache +------------------------ + +Please note this is for now an experimental feature. As a personnal note +from the module author, it should be safe to use. + +Use this cache class setting to enable compression. This will save usually +about 80% RAM at the cost of some milliseconds server time. + + $conf['cache_default_class'] = 'Redis_CacheCompressed'; + +Additionnaly, you can alter the default size compression threshold, under which +entries will not be compressed (size is in bytes, set 0 to always compress): + + $conf['cache_compression_size_threshold'] = 100; + +You can also change the compression level, which an positive integer between +1 and 9, 1 being the lowest but fastest compression ratio, 9 being the most +aggressive compression but is a lot slower. From testing, setting it to the +lower level (1) gives 80% memory usage decrease, which is more than enough. + + $conf['cache_compression_ratio'] = 5; + +Please note that those settings are global and not on a cache bin basis, you can +already control whenever the compression is to be used or not by selecting a +different cache class on per cache bin basis. + +If you switch from the standard default backend (without compression) to the +compressed cache backend, it will recover transparently uncompressed data and +proceed normally without additional cache eviction, it safe to upgrade. +Donwgrading from compressed data to uncompressed data won't work, but the +cache backend will just give you cache hit miss and it will work seamlessly +too without any danger for the site. + +Choose the Redis client library to use +-------------------------------------- + +Add into your settings.php file: + + $conf['redis_client_interface'] = 'PhpRedis'; + +You can replace 'PhpRedis' with 'Predis', depending on the library you chose. + +Note that this is optional but recommended. If you don't set this variable the +module will proceed to class lookups and attempt to choose the best client +available, having always a preference for the Predis one. + +Tell Drupal to use the cache backend +------------------------------------ + +Usual cache backend configuration, as follows, to add into your settings.php +file like any other backend: + + $conf['cache_backends'][] = 'sites/all/modules/redis/redis.autoload.inc'; + $conf['cache_class_cache'] = 'Redis_Cache'; + $conf['cache_class_cache_menu'] = 'Redis_Cache'; + $conf['cache_class_cache_bootstrap'] = 'Redis_Cache'; + // ... Any other bins. + +Tell Drupal to use the lock backend +----------------------------------- + +Usual lock backend override, update you settings.php file as this: + + $conf['lock_inc'] = 'sites/all/modules/redis/redis.lock.inc'; + +Tell Drupal to use the path alias backend +----------------------------------------- + +Usual path backend override, update you settings.php file as this: + + $conf['path_inc'] = 'sites/all/modules/redis/redis.path.inc'; + +Notice that there is an additional variable for path handling that is set +per default which will ignore any path that is an admin path, gaining a few +SQL queries. If you want to be able to set aliases on admin path and restore +an almost default Drupal core behavior, you should add this line into your +settings.php file: + + $conf['path_alias_admin_blacklist'] = FALSE; + +Drupal 6 and lock backend +------------------------- + +Considering this is a Drupal 7 module only downloading it in Drupal 6 will make +the module UI telling you this module is unsupported yet you can use the lock +backend on Drupal 6. + +Read your Drupal 6 core documentation and use the redis.lock.inc file as +lock_inc replacement the same way its being done for Drupal 7 and it should +work. Note that this is untested by the module maintainer (feedback will be +greatly appreciated). + +Common settings +=============== + +Connect throught a UNIX socket +------------------------------ + +All you have to do is specify this line: + + $conf['redis_client_socket'] = '/some/path/redis.sock'; + +Both drivers support it. + +Connect to a remote host +------------------------ + +If your Redis instance is remote, you can use this syntax: + + $conf['redis_client_host'] = '1.2.3.4'; + $conf['redis_client_port'] = 1234; + +Port is optional, default is 6379 (default Redis port). + +Using a specific database +------------------------- + +Per default, Redis ships the database "0". All default connections will be use +this one if nothing is specified. + +Depending on you OS or OS distribution, you might have numerous database. To +use one in particular, just add to your settings.php file: + + $conf['redis_client_base'] = 12; + +Please note that if you are working in shard mode, you should never set this +variable. + +Connection to a password protected instance +------------------------------------------- + +If you are using a password protected instance, specify the password this way: + + $conf['redis_client_password'] = "mypassword"; + +Depending on the backend, using a wrong auth will behave differently: + + - Predis will throw an exception and make Drupal fail during early boostrap. + + - PhpRedis will make Redis calls silent and creates some PHP warnings, thus + Drupal will behave as if it was running with a null cache backend (no cache + at all). + +Prefixing site cache entries (avoiding sites name collision) +------------------------------------------------------------ + +If you need to differenciate multiple sites using the same Redis instance and +database, you will need to specify a prefix for your site cache entries. + +Important note: most people don't need that feature since that when no prefix +is specified, the Redis module will attempt to use the a hash of the database +credentials in order to provide a multisite safe default behavior. This means +that the module will also safely work in CLI scripts. + +Cache prefix configuration attemps to use a unified variable accross contrib +backends that support this feature. This variable name is 'cache_prefix'. + +This variable is polymorphic, the simplest version is to provide a raw string +that will be the default prefix for all cache bins: + + $conf['cache_prefix'] = 'mysite_'; + +Alternatively, to provide the same functionality, you can provide the variable +as an array: + + $conf['cache_prefix']['default'] = 'mysite_'; + +This allows you to provide different prefix depending on the bin name. Common +usage is that each key inside the 'cache_prefix' array is a bin name, the value +the associated prefix. If the value is explicitely FALSE, then no prefix is +used for this bin. + +The 'default' meta bin name is provided to define the default prefix for non +specified bins. It behaves like the other names, which means that an explicit +FALSE will order the backend not to provide any prefix for any non specified +bin. + +Here is a complex sample: + + // Default behavior for all bins, prefix is 'mysite_'. + $conf['cache_prefix']['default'] = 'mysite_'; + + // Set no prefix explicitely for 'cache' and 'cache_bootstrap' bins. + $conf['cache_prefix']['cache'] = FALSE; + $conf['cache_prefix']['cache_bootstrap'] = FALSE; + + // Set another prefix for 'cache_menu' bin. + $conf['cache_prefix']['cache_menu'] = 'menumysite_'; + +Note that this last notice is Redis only specific, because per default Redis +server will not namespace data, thus sharing an instance for multiple sites +will create conflicts. This is not true for every contributed backends. + +Sharding vs normal mode +----------------------- + +Per default the Redis cache backend will be in "normal" mode, meaning that +every flush call will trigger and EVAL lua script that will proceed to cache +wipeout and cleanup the Redis database from stalled entries. + +Nevertheless, if you are working with a Redis server < 2.6 or in a sharded +environment, you cannot multiple keys per command nor proceed to EVAL'ed +scripts, you will then need to switch to the sharded mode. + +Sharded mode will never delete entries on flush calls, but register a key +with the current flush time instead. Cache entries will then be deleted on +read if the entry checksum does not match or is older than the latest flush +call. Note that this mode is fast and safe, but must be used accordingly +with the default lifetime for permanent items, else your Redis server might +keep stalled entries into its database forever. + +In order to enable the sharded mode, set into your settings.php file: + + $conf['redis_flush_mode'] = 3; + +Please note that the value 3 is there to keep backward compatibility with +older versions of the Redis module and will not change. + +Note that previous Redis module version allowed to set a per-bin setting for +the clear mode value; nevertheless the clear mode is not a valid setting +anymore and the past issues have been resolved. Only the global value will +work as of now. + +Sharding and pipelining +----------------------- + +Whe using this module with sharding mode you may have a sharding proxy able to +do command pipelining. If that is the case, you should switch to "sharding with +pipelining" mode instead: + + $conf['redis_flush_mode'] = 4; + +Note that if you use the sharding mode because you use an older version of the +Redis server, you should always use this mode to ensure the best performances. + +Default lifetime for permanent items +------------------------------------ + +Redis when reaching its maximum memory limit will stop writing data in its +storage engine: this is a feature that avoid the Redis server crashing when +there is no memory left on the machine. + +As a workaround, Redis can be configured as a LRU cache for both volatile or +permanent items, which means it can behave like Memcache; Problem is that if +you use Redis as a permanent storage for other business matters than this +module you cannot possibly configure it to drop permanent items or you'll +loose data. + +This workaround allows you to explicity set a very long or configured default +lifetime for CACHE_PERMANENT items (that would normally be permanent) which +will mark them as being volatile in Redis storage engine: this then allows you +to configure a LRU behavior for volatile keys without engaging the permenent +business stuff in a dangerous LRU mechanism; Cache items even if permament will +be dropped when unused using this. + +Per default the TTL for permanent items will set to safe-enough value which is +one year; No matter how Redis will be configured default configuration or lazy +admin will inherit from a safe module behavior with zero-conf. + +For advanturous people, you can manage the TTL on a per bin basis and change +the default one: + + // Make CACHE_PERMANENT items being permanent once again + // 0 is a special value usable for all bins to explicitely tell the + // cache items will not be volatile in Redis. + $conf['redis_perm_ttl'] = 0; + + // Make them being volatile with a default lifetime of 1 year. + $conf['redis_perm_ttl'] = "1 year"; + + // You can override on a per-bin basis; + // For example make cached field values live only 3 monthes: + $conf['redis_perm_ttl_cache_field'] = "3 months"; + + // But you can also put a timestamp in there; In this case the + // value must be a STRICTLY TYPED integer: + $conf['redis_perm_ttl_cache_field'] = 2592000; // 30 days. + +Time interval string will be parsed using DateInterval::createFromDateString +please refer to its documentation: + + http://www.php.net/manual/en/dateinterval.createfromdatestring.php + +Please also be careful about the fact that those settings are overriden by +the 'cache_lifetime' Drupal variable, which should always be set to 0. +Moreover, this setting will affect all cache entries without exception so +be careful and never set values too low if you don't want this setting to +override default expire value given by modules on temporary cache entries. + +Lock backends +------------- + +Both implementations provides a Redis lock backend. Redis lock backend proved to +be faster than the default SQL based one when using both servers on the same box. + +Both backends, thanks to the Redis WATCH, MULTI and EXEC commands provides a +real race condition free mutexes by using Redis transactions. + +Queue backend +------------- + +This module provides an experimental queue backend. It is for now implemented +only using the PhpRedis driver, any attempt to use it using Predis will result +in runtime errors. + +If you want to change the queue driver system wide, set this into your +setting.php file: + + $conf['queue_default_class'] = 'Redis_Queue'; + $conf['queue_default_reliable_class'] = 'Redis_Queue'; + +Note that some queue implementations such as the batch queue are hardcoded +within Drupal and will always use a database dependent implementation. + +If you need to proceed with finer tuning, you can set a per-queue class in +such way: + + $conf['queue_class_NAME'] = 'Redis_Queue'; + +Where NAME is the arbitrary module given queue name, used as first parameter +for the method DrupalQueue::get(). + +THIS IS STILL VERY EXPERIMENTAL. The queue should work without any problems +except it does not implement the item lease time correctly, this means that +items that are too long to process won't be released back and forth but will +block the thread processing it instead. This is the only side effect I am +aware of at the current time. + +Failover, sharding and partionning +================================== + +Important notice +---------------- + +There are numerous support and feature request issues about client sharding, +failover ability, multi-server connection, ability to read from slave and +server clustering opened in the issue queue. Note that there is not one +universally efficient solution for this: most of the solutions require that +you cannot use the MULTI/EXEC command using more than one key, and that you +cannot use complex UNION and intersection features anymore. + +This module does not implement any kind of client side key hashing or sharding +and never intended to; We recommend that you read the official Redis +documentation page about partionning. + +The best solution for clustering and sharding today seems to be the proxy +assisted partionning using tools such as Twemproxy. + +Current components state +------------------------ + +As of now, provided components are simple enough so they never use WATCH or +MULTI/EXEC transaction blocks on multiple keys : this means that you can use +them in an environment doing data sharding/partionning. This remains true +except when you use a proxy that blocks those commands such as Twemproxy. + +Lock +---- + +Lock backend works on a single key per lock, it theorically guarantees the +atomicity of operations therefore is usable in a sharded environement. Sadly +if you use proxy assisted sharding such as Twemproxy, WATCH, MULTI and EXEC +commands won't pass making it non shardable. + +Path +---- + +Path backend does not use on transactions, it is safe to use in a sharded +environment. Note that this backend uses a single HASH key per language +and per way (alias to source or source to alias) and therefore won't benefit +greatly if not at all from being sharded. + +Cache +----- + +Cache uses pipelined transactions but does not uses it to guarantee any kind +of data consistency. If you use a smart sharding proxy it is supposed to work +transparently without any hickups. + +Queue +----- + +Queue is still in development. There might be problems in the long term for +this component in sharded environments. + +Testing +======= + +Due to Drupal unit testing API being incredibly stupid, the unit tests can only +work with PHP >=5.3 while the module will work gracefully with PHP 5.2 (at least +using the PhpRedis client). + +I did not find any hint about making tests being configurable, so per default +the tested Redis server must always be on localhost with default configuration. diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/AbstractBackend.php b/sites/all/modules/contrib/dev/redis/lib/Redis/AbstractBackend.php new file mode 100644 index 00000000..2ccedb7f --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/AbstractBackend.php @@ -0,0 +1,108 @@ +client = $client; + $this->prefix = $prefix; + + if (null !== $namespace) { + $this->namespace = $namespace; + } + } + + final public function setClient($client) + { + $this->client = $client; + } + + final public function getClient() + { + return $this->client; + } + + final public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + final public function getPrefix() + { + return $this->prefix; + } + + final public function setNamespace($namespace) + { + $this->namespace = $namespace; + } + + final public function getNamespace() + { + return $this->namespace; + } + + /** + * Get prefixed key + * + * @param string|string[] $parts + * Arbitrary number of strings to compose the key + * + * @return string + */ + public function getKey($parts = array()) + { + $key = array(); + + if (null !== $this->prefix) { + $key[] = $this->prefix; + } + if (null !== $this->namespace) { + $key[] = $this->namespace; + } + + if ($parts) { + if (is_array($parts)) { + foreach ($parts as $part) { + if ($part) { + $key[] = $part; + } + } + } else { + $key[] = $parts; + } + } + + return implode(self::KEY_SEPARATOR, array_filter($key)); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/BackendInterface.php b/sites/all/modules/contrib/dev/redis/lib/Redis/BackendInterface.php new file mode 100644 index 00000000..7cda8e39 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/BackendInterface.php @@ -0,0 +1,59 @@ += 2.6 (older version don't + * support EVAL). + */ + const FLUSH_NORMAL = 0; + + /** + * This mode is tailored for sharded Redis servers instances usage: it + * will never delete entries but only mark the latest flush timestamp + * into one of the servers in the shard. It will proceed to delete on + * read single entries when invalid entries are being loaded. + */ + const FLUSH_SHARD = 3; + + /** + * Same as the one above, plus attempt to do pipelining when possible. + * + * This is supposed to work with sharding proxies that supports + * pipelining themselves, such as Twemproxy. + */ + const FLUSH_SHARD_WITH_PIPELINING = 4; + + /** + * Computed keys are let's say arround 60 characters length due to + * key prefixing, which makes 1,000 keys DEL command to be something + * arround 50,000 bytes length: this is huge and may not pass into + * Redis, let's split this off. + * Some recommend to never get higher than 1,500 bytes within the same + * command which makes us forced to split this at a very low threshold: + * 20 seems a safe value here (1,280 average length). + */ + const KEY_THRESHOLD = 20; + + /** + * @var Redis_Cache_BackendInterface + */ + protected $backend; + + /** + * @var string + */ + protected $bin; + + /** + * When the global 'cache_lifetime' Drupal variable is set to a value, the + * cache backends should not expire temporary entries by themselves per + * Drupal signature. Volatile items will be dropped accordingly to their + * set lifetime. + * + * @var boolean + */ + protected $allowTemporaryFlush = true; + + /** + * When in shard mode, the backend cannot proceed to multiple keys + * operations, and won't delete keys on flush calls. + * + * @var boolean + */ + protected $isSharded = false; + + /** + * When in shard mode, the proxy may or may not support pipelining, + * Twemproxy is known to support it. + * + * @var boolean + */ + protected $allowPipeline = false; + + /** + * Default TTL for CACHE_PERMANENT items. + * + * See "Default lifetime for permanent items" section of README.txt + * file for a comprehensive explaination of why this exists. + * + * @var int + */ + protected $permTtl = self::LIFETIME_PERM_DEFAULT; + + /** + * Maximum TTL for this bin from Drupal configuration. + * + * @var int + */ + protected $maxTtl = 0; + + /** + * Flush permanent and volatile cached values + * + * @var string[] + * First value is permanent latest flush time and second value + * is volatile latest flush time + */ + protected $flushCache = null; + + /** + * Is this bin in shard mode + * + * @return boolean + */ + public function isSharded() + { + return $this->isSharded; + } + + /** + * Does this bin allow pipelining through sharded environment + * + * @return boolean + */ + public function allowPipeline() + { + return $this->allowPipeline; + } + + /** + * Does this bin allow temporary item flush + * + * @return boolean + */ + public function allowTemporaryFlush() + { + return $this->allowTemporaryFlush; + } + + /** + * Get TTL for CACHE_PERMANENT items. + * + * @return int + * Lifetime in seconds. + */ + public function getPermTtl() + { + return $this->permTtl; + } + + /** + * Get maximum TTL for all items. + * + * @return int + * Lifetime in seconds. + */ + public function getMaxTtl() + { + return $this->maxTtl; + } + + /** + * {@inheritdoc} + */ + public function __construct($bin) + { + $this->bin = $bin; + + $className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_CACHE); + $this->backend = new $className(Redis_Client::getClient(), $bin, Redis_Client::getDefaultPrefix($bin)); + + $this->refreshCapabilities(); + $this->refreshPermTtl(); + $this->refreshMaxTtl(); + } + + /** + * Find from Drupal variables the clear mode. + */ + public function refreshCapabilities() + { + if (0 < variable_get('cache_lifetime', 0)) { + // Per Drupal default behavior, when the 'cache_lifetime' variable + // is set we must not flush any temporary items since they have a + // life time. + $this->allowTemporaryFlush = false; + } + + if (null !== ($mode = variable_get('redis_flush_mode', null))) { + $mode = (int)$mode; + } else { + $mode = self::FLUSH_NORMAL; + } + + $this->isSharded = self::FLUSH_SHARD === $mode || self::FLUSH_SHARD_WITH_PIPELINING === $mode; + $this->allowPipeline = self::FLUSH_SHARD !== $mode; + } + + /** + * Find from Drupal variables the right permanent items TTL. + */ + protected function refreshPermTtl() + { + $ttl = null; + if (null === ($ttl = variable_get('redis_perm_ttl_' . $this->bin, null))) { + if (null === ($ttl = variable_get('redis_perm_ttl', null))) { + $ttl = self::LIFETIME_PERM_DEFAULT; + } + } + if ($ttl === (int)$ttl) { + $this->permTtl = $ttl; + } else { + if ($iv = DateInterval::createFromDateString($ttl)) { + // http://stackoverflow.com/questions/14277611/convert-dateinterval-object-to-seconds-in-php + $this->permTtl = ($iv->y * 31536000 + $iv->m * 2592000 + $iv->d * 86400 + $iv->h * 3600 + $iv->i * 60 + $iv->s); + } else { + // Sorry but we have to log this somehow. + trigger_error(sprintf("Parsed TTL '%s' has an invalid value: switching to default", $ttl)); + $this->permTtl = self::LIFETIME_PERM_DEFAULT; + } + } + } + + /** + * Find from Drupal variables the maximum cache lifetime. + */ + public function refreshMaxTtl() + { + // And now cache lifetime. Be aware we exclude negative values + // considering those are Drupal misconfiguration. + $maxTtl = variable_get('cache_lifetime', 0); + if (0 < $maxTtl) { + if ($maxTtl < $this->permTtl) { + $this->maxTtl = $maxTtl; + } else { + $this->maxTtl = $this->permTtl; + } + } else if ($this->permTtl) { + $this->maxTtl = $this->permTtl; + } + } + + /** + * Set last flush time + * + * @param string $permanent + * @param string $volatile + */ + public function setLastFlushTime($permanent = false, $volatile = false) + { + // Here we need to fetch absolute values from backend, to avoid + // concurrency problems and ensure data validity. + list($flushPerm, $flushVolatile) = $this->backend->getLastFlushTime(); + + $checksum = $this->getValidChecksum( + max(array( + $flushPerm, + $flushVolatile, + $permanent, + time(), + )) + ); + + if ($permanent) { + $this->backend->setLastFlushTimeFor($checksum, false); + $this->backend->setLastFlushTimeFor($checksum, true); + $this->flushCache = array($checksum, $checksum); + } else if ($volatile) { + $this->backend->setLastFlushTimeFor($checksum, true); + $this->flushCache = array($flushPerm, $checksum); + } + } + + /** + * Get latest flush time + * + * @return string[] + * First value is the latest flush time for permanent entries checksum, + * second value is the latest flush time for volatile entries checksum. + */ + public function getLastFlushTime() + { + if (!$this->flushCache) { + $this->flushCache = $this->backend->getLastFlushTime(); + } + + // At the very first hit, we might not have the timestamps set, thus + // we need to create them to avoid our entry being considered as + // invalid + if (!$this->flushCache[0]) { + $this->setLastFlushTime(true, true); + } else if (!$this->flushCache[1]) { + $this->setLastFlushTime(false, true); + } + + return $this->flushCache; + } + + /** + * Create cache entry + * + * @param string $cid + * @param mixed $data + * + * @return array + */ + protected function createEntryHash($cid, $data, $expire = CACHE_PERMANENT) + { + list($flushPerm, $flushVolatile) = $this->getLastFlushTime(); + + if (CACHE_TEMPORARY === $expire) { + $validityThreshold = max(array($flushVolatile, $flushPerm)); + } else { + $validityThreshold = $flushPerm; + } + + $time = $this->getValidChecksum($validityThreshold); + + $hash = array( + 'cid' => $cid, + 'created' => $time, + 'expire' => $expire, + ); + + // Let Redis handle the data types itself. + if (!is_string($data)) { + $hash['data'] = serialize($data); + $hash['serialized'] = 1; + } else { + $hash['data'] = $data; + $hash['serialized'] = 0; + } + + return $hash; + } + + /** + * Expand cache entry from fetched data + * + * @param array $values + * Raw values fetched from Redis server data + * + * @return array + * Or FALSE if entry is invalid + */ + protected function expandEntry(array $values, $flushPerm, $flushVolatile) + { + // Check for entry being valid. + if (empty($values['cid'])) { + return; + } + + // This ensures backward compatibility with older version of + // this module's data still stored in Redis. + if (isset($values['expire'])) { + $expire = (int)$values['expire']; + // Ensure the entry is valid and have not expired. + if ($expire !== CACHE_PERMANENT && $expire !== CACHE_TEMPORARY && $expire <= time()) { + return false; + } + } + + // Ensure the entry does not predate the last flush time. + if ($this->allowTemporaryFlush && !empty($values['volatile'])) { + $validityThreshold = max(array($flushPerm, $flushVolatile)); + } else { + $validityThreshold = $flushPerm; + } + + if ($values['created'] <= $validityThreshold) { + return false; + } + + $entry = (object)$values; + + // Reduce the checksum to the real timestamp part + $entry->created = (int)$entry->created; + + if ($entry->serialized) { + $entry->data = unserialize($entry->data); + } + + return $entry; + } + + /** + * {@inheritdoc} + */ + public function get($cid) + { + $values = $this->backend->get($cid); + + if (empty($values)) { + return false; + } + + list($flushPerm, $flushVolatile) = $this->getLastFlushTime(); + + $entry = $this->expandEntry($values, $flushPerm, $flushVolatile); + + if (!$entry) { // This entry exists but is invalid. + $this->backend->delete($cid); + return false; + } + + return $entry; + } + + /** + * {@inheritdoc} + */ + public function getMultiple(&$cids) + { + $ret = array(); + $delete = array(); + + if (!$this->allowPipeline) { + $entries = array(); + foreach ($cids as $cid) { + if ($entry = $this->backend->get($cid)) { + $entries[$cid] = $entry; + } + } + } else { + $entries = $this->backend->getMultiple($cids); + } + + list($flushPerm, $flushVolatile) = $this->getLastFlushTime(); + + foreach ($cids as $key => $cid) { + if (!empty($entries[$cid])) { + $entry = $this->expandEntry($entries[$cid], $flushPerm, $flushVolatile); + } else { + $entry = null; + } + if (empty($entry)) { + $delete[] = $cid; + } else { + $ret[$cid] = $entry; + unset($cids[$key]); + } + } + + if (!empty($delete)) { + if ($this->allowPipeline) { + foreach ($delete as $id) { + $this->backend->delete($id); + } + } else { + $this->backend->deleteMultiple($delete); + } + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function set($cid, $data, $expire = CACHE_PERMANENT) + { + $hash = $this->createEntryHash($cid, $data, $expire); + $maxTtl = $this->getMaxTtl(); + + switch ($expire) { + + case CACHE_PERMANENT: + $this->backend->set($cid, $hash, $maxTtl, false); + break; + + case CACHE_TEMPORARY: + $this->backend->set($cid, $hash, $maxTtl, true); + break; + + default: + $ttl = $expire - time(); + // Ensure $expire consistency + if ($ttl <= 0) { + // Entry has already expired, but we may have a stalled + // older cache entry remaining there, ensure it wont + // happen by doing a preventive delete + $this->backend->delete($cid); + } else { + if ($maxTtl && $maxTtl < $ttl) { + $ttl = $maxTtl; + } + $this->backend->set($cid, $hash, $ttl, false); + } + break; + } + } + + /** + * {@inheritdoc} + */ + public function clear($cid = null, $wildcard = false) + { + if (null === $cid && !$wildcard) { + // Drupal asked for volatile entries flush, this will happen + // during cron run, mostly + $this->setLastFlushTime(false, true); + + if (!$this->isSharded && $this->allowTemporaryFlush) { + $this->backend->flushVolatile(); + } + } else if ($wildcard) { + if (empty($cid)) { + // This seems to be an error, just do nothing. + return; + } + + if ('*' === $cid) { + // Use max() to ensure we invalidate both correctly + $this->setLastFlushTime(true); + + if (!$this->isSharded) { + $this->backend->flush(); + } + } else { + if (!$this->isSharded) { + $this->backend->deleteByPrefix($cid); + } else { + // @todo This needs a map algorithm the same way memcache + // module implemented it for invalidity by prefixes. This + // is a very stupid fallback + $this->setLastFlushTime(true); + } + } + } else if (is_array($cid)) { + $this->backend->deleteMultiple($cid); + } else { + $this->backend->delete($cid); + } + } + + public function isEmpty() + { + return false; + } + + /** + * From the given timestamp build an incremental safe time-based identifier. + * + * Due to potential accidental cache wipes, when a server goes down in the + * cluster or when a server triggers its LRU algorithm wipe-out, keys that + * matches flush or tags checksum might be dropped. + * + * Per default, each new inserted tag will trigger a checksum computation to + * be stored in the Redis server as a timestamp. In order to ensure a checksum + * validity a simple comparison between the tag checksum and the cache entry + * checksum will tell us if the entry pre-dates the current checksum or not, + * thus telling us its state. The main problem we experience is that Redis + * is being so fast it is able to create and drop entries at same second, + * sometime even the same micro second. The only safe way to avoid conflicts + * is to checksum using an arbitrary computed number (a sequence). + * + * Drupal core does exactly this thus tags checksums are additions of each tag + * individual checksum; each tag checksum is a independent arbitrary serial + * that gets incremented starting with 0 (no invalidation done yet) to n (n + * invalidations) which grows over time. This way the checksum computation + * always rises and we have a sensible default that works in all cases. + * + * This model works as long as you can ensure consistency for the serial + * storage over time. Nevertheless, as explained upper, in our case this + * serial might be dropped at some point for various valid technical reasons: + * if we start over to 0, we may accidentally compute a checksum which already + * existed in the past and make invalid entries turn back to valid again. + * + * In order to prevent this behavior, using a timestamp as part of the serial + * ensures that we won't experience this problem in a time range wider than a + * single second, which is safe enough for us. But using timestamp creates a + * new problem: Redis is so fast that we can set or delete hundreds of entries + * easily during the same second: an entry created then invalidated the same + * second will create false positives (entry is being considered as valid) - + * note that depending on the check algorithm, false negative may also happen + * the same way. Therefore we need to have an abitrary serial value to be + * incremented in order to enforce our checks to be more strict. + * + * The solution to both the first (the need for a time based checksum in case + * of checksum data being dropped) and the second (the need to have an + * arbitrary predictible serial value to avoid false positives or negatives) + * we are combining the two: every checksum will be built this way: + * + * UNIXTIMESTAMP.SERIAL + * + * For example: + * + * 1429789217.017 + * + * will reprensent the 17th invalidation of the 1429789217 exact second which + * happened while writing this documentation. The next tag being invalidated + * the same second will then have this checksum: + * + * 1429789217.018 + * + * And so on... + * + * In order to make it consitent with PHP string and float comparison we need + * to set fixed precision over the decimal, and store as a string to avoid + * possible float precision problems when comparing. + * + * This algorithm is not fully failsafe, but allows us to proceed to 1000 + * operations on the same checksum during the same second, which is a + * sufficiently great value to reduce the conflict probability to almost + * zero for most uses cases. + * + * @param int|string $timestamp + * "TIMESTAMP[.INCREMENT]" string + * + * @return string + * The next "TIMESTAMP.INCREMENT" string. + */ + public function getNextIncrement($timestamp = null) + { + if (!$timestamp) { + return time() . '.000'; + } + + if (false !== ($pos = strpos($timestamp, '.'))) { + $inc = substr($timestamp, $pos + 1, 3); + + return ((int)$timestamp) . '.' . str_pad($inc + 1, 3, '0', STR_PAD_LEFT); + } + + return $timestamp . '.000'; + } + + /** + * Get valid checksum + * + * @param int|string $previous + * "TIMESTAMP[.INCREMENT]" string + * + * @return string + * The next "TIMESTAMP.INCREMENT" string. + * + * @see Redis_Cache::getNextIncrement() + */ + public function getValidChecksum($previous = null) + { + if (time() === (int)$previous) { + return $this->getNextIncrement($previous); + } else { + return $this->getNextIncrement(); + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Cache/BackendInterface.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Cache/BackendInterface.php new file mode 100644 index 00000000..5a1f6c2e --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Cache/BackendInterface.php @@ -0,0 +1,102 @@ +getClient(); + $key = $this->getKey(self::LAST_FLUSH_KEY); + + if ($volatile) { + $client->hset($key, 'volatile', $time); + } else { + $client->hmset($key, array( + 'permanent' => $time, + 'volatile' => $time, + )); + } + } + + public function getLastFlushTime() + { + $client = $this->getClient(); + $key = $this->getKey(self::LAST_FLUSH_KEY); + $values = $client->hmget($key, array("permanent", "volatile")); + + if (empty($values) || !is_array($values)) { + $ret = array(0, 0); + } else { + if (empty($values['permanent'])) { + $values['permanent'] = 0; + } + if (empty($values['volatile'])) { + $values['volatile'] = 0; + } + $ret = array($values['permanent'], $values['volatile']); + } + + return $ret; + } + + public function get($id) + { + $client = $this->getClient(); + $key = $this->getKey($id); + $values = $client->hgetall($key); + + // Recent versions of PhpRedis will return the Redis instance + // instead of an empty array when the HGETALL target key does + // not exists. I see what you did there. + if (empty($values) || !is_array($values)) { + return false; + } + + return $values; + } + + public function getMultiple(array $idList) + { + $client = $this->getClient(); + + $ret = array(); + + $pipe = $client->multi(Redis::PIPELINE); + foreach ($idList as $id) { + $pipe->hgetall($this->getKey($id)); + } + $replies = $pipe->exec(); + + foreach (array_values($idList) as $line => $id) { + if (!empty($replies[$line]) && is_array($replies[$line])) { + $ret[$id] = $replies[$line]; + } + } + + return $ret; + } + + public function set($id, $data, $ttl = null, $volatile = false) + { + // Ensure TTL consistency: if the caller gives us an expiry timestamp + // in the past the key will expire now and will never be read. + // Behavior between Predis and PhpRedis seems to change here: when + // setting a negative expire time, PhpRedis seems to ignore the + // command and leave the key permanent. + if (null !== $ttl && $ttl <= 0) { + return; + } + + $data['volatile'] = (int)$volatile; + + $client = $this->getClient(); + $key = $this->getKey($id); + + $pipe = $client->multi(Redis::PIPELINE); + $pipe->hmset($key, $data); + + if (null !== $ttl) { + $pipe->expire($key, $ttl); + } + $pipe->exec(); + } + + public function delete($id) + { + $this->getClient()->del($this->getKey($id)); + } + + public function deleteMultiple(array $idList) + { + $client = $this->getClient(); + + $pipe = $client->multi(Redis::PIPELINE); + foreach ($idList as $id) { + $pipe->del($this->getKey($id)); + } + // Don't care if something failed. + $pipe->exec(); + } + + public function deleteByPrefix($prefix) + { + $client = $this->getClient(); + $ret = $client->eval(self::EVAL_DELETE_PREFIX, array($this->getKey($prefix . '*'))); + if (1 != $ret) { + trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR); + } + } + + public function flush() + { + $client = $this->getClient(); + $ret = $client->eval(self::EVAL_DELETE_PREFIX, array($this->getKey('*'))); + if (1 != $ret) { + trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR); + } + } + + public function flushVolatile() + { + $client = $this->getClient(); + $ret = $client->eval(self::EVAL_DELETE_VOLATILE, array($this->getKey('*'))); + if (1 != $ret) { + trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR); + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php new file mode 100644 index 00000000..376b248e --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php @@ -0,0 +1,145 @@ +getClient(); + $key = $this->getKey(self::LAST_FLUSH_KEY); + + if ($volatile) { + $client->hset($key, 'volatile', $time); + } else { + $client->hmset($key, array( + 'permanent' => $time, + 'volatile' => $time, + )); + } + } + + public function getLastFlushTime() + { + $client = $this->getClient(); + $key = $this->getKey(self::LAST_FLUSH_KEY); + $values = $client->hmget($key, array("permanent", "volatile")); + + if (empty($values) || !is_array($values)) { + $values = array(0, 0); + } else { + if (empty($values[0])) { + $values[0] = 0; + } + if (empty($values[1])) { + $values[1] = 0; + } + } + + return $values; + } + + public function get($id) + { + $client = $this->getClient(); + $key = $this->getKey($id); + $values = $client->hgetall($key); + + // Recent versions of PhpRedis will return the Redis instance + // instead of an empty array when the HGETALL target key does + // not exists. I see what you did there. + if (empty($values) || !is_array($values)) { + return false; + } + + return $values; + } + + public function getMultiple(array $idList) + { + $ret = array(); + + $pipe = $this->getClient()->pipeline(); + foreach ($idList as $id) { + $pipe->hgetall($this->getKey($id)); + } + $replies = $pipe->execute(); + + foreach (array_values($idList) as $line => $id) { + // HGETALL signature seems to differ depending on Predis versions. + // This was found just after Predis update. Even though I'm not sure + // this comes from Predis or just because we're misusing it. + if (!empty($replies[$line]) && is_array($replies[$line])) { + $ret[$id] = $replies[$line]; + } + } + + return $ret; + } + + public function set($id, $data, $ttl = null, $volatile = false) + { + // Ensure TTL consistency: if the caller gives us an expiry timestamp + // in the past the key will expire now and will never be read. + // Behavior between Predis and PhpRedis seems to change here: when + // setting a negative expire time, PhpRedis seems to ignore the + // command and leave the key permanent. + if (null !== $ttl && $ttl <= 0) { + return; + } + + $key = $this->getKey($id); + + $data['volatile'] = (int)$volatile; + + $pipe = $this->getClient()->pipeline(); + $pipe->hmset($key, $data); + if (null !== $ttl) { + $pipe->expire($key, $ttl); + } + $pipe->execute(); + } + + public function delete($id) + { + $client = $this->getClient(); + $client->del($this->getKey($id)); + } + + public function deleteMultiple(array $idList) + { + $pipe = $this->getClient()->pipeline(); + foreach ($idList as $id) { + $pipe->del($this->getKey($id)); + } + $pipe->execute(); + } + + public function deleteByPrefix($prefix) + { + $client = $this->getClient(); + $ret = $client->eval(self::EVAL_DELETE_PREFIX, 0, $this->getKey($prefix . '*')); + if (1 != $ret) { + trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR); + } + } + + public function flush() + { + $client = $this->getClient(); + $ret = $client->eval(self::EVAL_DELETE_PREFIX, 0, $this->getKey('*')); + if (1 != $ret) { + trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR); + } + } + + public function flushVolatile() + { + $client = $this->getClient(); + $ret = $client->eval(self::EVAL_DELETE_VOLATILE, 0, $this->getKey('*')); + if (1 != $ret) { + trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR); + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/CacheCompressed.php b/sites/all/modules/contrib/dev/redis/lib/Redis/CacheCompressed.php new file mode 100644 index 00000000..f817cb1f --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/CacheCompressed.php @@ -0,0 +1,66 @@ +compressionSizeThreshold = (int)variable_get('cache_compression_size_threshold', 100); + if ($this->compressionSizeThreshold < 0) { + trigger_error('cache_compression_size_threshold must be 0 or a positive integer, negative value found, switching back to default 100', E_USER_WARNING); + $this->compressionSizeThreshold = 100; + } + + // Minimum compression level (1) has good ratio in low time. + $this->compressionRatio = (int)variable_get('cache_compression_ratio', 1); + if ($this->compressionRatio < 1 || 9 < $this->compressionRatio) { + trigger_error('cache_compression_ratio must be between 1 and 9, out of bounds value found, switching back to default 1', E_USER_WARNING); + $this->compressionRatio = 1; + } + } + + /** + * {@inheritdoc} + */ + protected function createEntryHash($cid, $data, $expire = CACHE_PERMANENT) + { + $hash = parent::createEntryHash($cid, $data, $expire); + + // Empiric level when compression makes sense. + if (!$this->compressionSizeThreshold || strlen($hash['data']) > $this->compressionSizeThreshold) { + + $hash['data'] = gzcompress($hash['data'], $this->compressionRatio); + $hash['compressed'] = true; + } + + return $hash; + } + + /** + * {@inheritdoc} + */ + protected function expandEntry(array $values, $flushPerm, $flushVolatile) + { + if (!empty($values['data']) && !empty($values['compressed'])) { + // Uncompress, suppress warnings e.g. for broken CRC32. + $values['data'] = @gzuncompress($values['data']); + + // In such cases, void the cache entry. + if ($values['data'] === false) { + return false; + } + } + + return parent::expandEntry($values, $flushPerm, $flushVolatile); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Client.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Client.php new file mode 100644 index 00000000..a85cee58 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Client.php @@ -0,0 +1,241 @@ +getClient(); + } + + /** + * Get specific class implementing the current client usage for the specific + * asked core subsystem. + * + * @param string $system + * One of the Redis_Client::IMPL_* constant. + * @param string $clientName + * Client name, if fixed. + * + * @return string + * Class name, if found. + * + * @deprecated + */ + static public function getClass($system) + { + $class = $system . self::getClientInterfaceName(); + + if (!class_exists($class)) { + throw new Exception(sprintf("Class '%s' does not exist", $class)); + } + + return $class; + } +} + diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Client/FactoryInterface.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Client/FactoryInterface.php new file mode 100644 index 00000000..f53cad94 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Client/FactoryInterface.php @@ -0,0 +1,32 @@ +factory = $factory; + $this->serverList = $serverList; + } + + /** + * Get client for the given realm + * + * @param string $realm + * @param boolean $allowDefault + * + * @return mixed + */ + public function getClient($realm = self::REALM_DEFAULT, $allowDefault = true) + { + if (!isset($this->clients[$realm])) { + $client = $this->createClient($realm); + + if (false === $client) { + if (self::REALM_DEFAULT !== $realm && $allowDefault) { + $this->clients[$realm] = $this->getClient(self::REALM_DEFAULT); + } else { + throw new InvalidArgumentException(sprintf("Could not find client for realm '%s'", $realm)); + } + } else { + $this->clients[$realm] = $client; + } + } + + return $this->clients[$realm]; + } + + /** + * Build connection parameters array from current Drupal settings + * + * @param string $realm + * + * @return boolean|string[] + * A key-value pairs of configuration values or false if realm is + * not defined per-configuration + */ + private function buildOptions($realm) + { + $info = null; + + if (isset($this->serverList[$realm])) { + $info = $this->serverList[$realm]; + } else { + return false; + } + + $info += array( + 'host' => self::REDIS_DEFAULT_HOST, + 'port' => self::REDIS_DEFAULT_PORT, + 'base' => self::REDIS_DEFAULT_BASE, + 'password' => self::REDIS_DEFAULT_PASSWORD, + 'socket' => self::REDIS_DEFAULT_SOCKET + ); + + return array_filter($info); + } + + /** + * Get client singleton + */ + private function createClient($realm) + { + $info = $this->buildOptions($realm); + + if (false === $info) { + return false; + } + + return $this->factory->getClient($info); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Client/PhpRedis.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Client/PhpRedis.php new file mode 100644 index 00000000..b5d72c2d --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Client/PhpRedis.php @@ -0,0 +1,36 @@ +connect($options['socket']); + } else { + $client->connect($options['host'], $options['port']); + } + + if (isset($options['password'])) { + $client->auth($options['password']); + } + + if (isset($options['base'])) { + $client->select($options['base']); + } + + // Do not allow PhpRedis serialize itself data, we are going to do it + // ourself. This will ensure less memory footprint on Redis size when + // we will attempt to store small values. + $client->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); + + return $client; + } + + public function getName() { + return 'PhpRedis'; + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php new file mode 100644 index 00000000..f5e2299c --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php @@ -0,0 +1,145 @@ + $value) { + if (!isset($value)) { + unset($options[$key]); + } + } + + // I'm not sure why but the error handler is driven crazy if timezone + // is not set at this point. + // Hopefully Drupal will restore the right one this once the current + // account has logged in. + date_default_timezone_set(@date_default_timezone_get()); + + $client = new \Predis\Client($options); + + if (isset($options['base']) && 0 !== $options['base']) { + $client->select((int)$options['base']); + } + + return $client; + } + + public function getName() { + return 'Predis'; + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php new file mode 100644 index 00000000..f5c9745e --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php @@ -0,0 +1,31 @@ + 0) { + // This function should only be called by a request that failed to get a + // lock, so we sleep first to give the parallel request a chance to finish + // and release the lock. + usleep($sleep); + // After each sleep, increase the value of $sleep until it reaches + // 500ms, to reduce the potential for a lock stampede. + $delay = $delay - $sleep; + $sleep = min(500000, $sleep + 25000, $delay); + if ($this->lockMayBeAvailable($name)) { + // No longer need to wait. + return FALSE; + } + } + // The caller must still wait longer to get the lock. + return TRUE; + } + + /** + * Default implementation from actual Drupal core. + * + * @see Redis_Lock_BackendInterface::getLockId() + */ + public function getLockId() { + if (!isset($this->_lockId)) { + $this->_lockId = uniqid(mt_rand(), TRUE); + // We only register a shutdown function if a lock is used. + drupal_register_shutdown_function('lock_release_all', $this->_lockId); + } + return $this->_lockId; + } + + /** + * Generate a redis key name for the current lock name + */ + public function getKey($name = null) { + if (null === $name) { + return parent::getKey('lock'); + } else { + return parent::getKey(array('lock', $name)); + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php new file mode 100644 index 00000000..f9ae845e --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php @@ -0,0 +1,138 @@ +getClient(); + $key = $this->getKey($name); + $id = $this->getLockId(); + + // Insure that the timeout is at least 1 second, we cannot do otherwise with + // Redis, this is a minor change to the function signature, but in real life + // nobody will notice with so short duration. + $timeout = ceil(max($timeout, 1)); + + // If we already have the lock, check for his owner and attempt a new EXPIRE + // command on it. + if (isset($this->_locks[$name])) { + + // Create a new transaction, for atomicity. + $client->watch($key); + + // Global tells us we are the owner, but in real life it could have expired + // and another process could have taken it, check that. + if ($client->get($key) != $id) { + // Explicit UNWATCH we are not going to run the MULTI/EXEC block. + $client->unwatch(); + unset($this->_locks[$name]); + return FALSE; + } + + // See https://github.com/phpredis/phpredis#watch-unwatch + // MULTI and other commands can fail, so we can't chain calls. + if (FALSE !== ($result = $client->multi())) { + $client->setex($key, $timeout, $id); + $result = $client->exec(); + } + + // Did it broke? + if (FALSE === $result) { + unset($this->_locks[$name]); + // Explicit transaction release which also frees the WATCH'ed key. + $client->discard(); + return FALSE; + } + + return ($this->_locks[$name] = TRUE); + } + else { + $client->watch($key); + $owner = $client->get($key); + + // If the $key is set they lock is not available + if (!empty($owner) && $id != $owner) { + $client->unwatch(); + return FALSE; + } + + // See https://github.com/phpredis/phpredis#watch-unwatch + // MULTI and other commands can fail, so we can't chain calls. + if (FALSE !== ($result = $client->multi())) { + $client->setex($key, $timeout, $id); + $result->exec(); + } + + // If another client modified the $key value, transaction will be discarded + // $result will be set to FALSE. This means atomicity have been broken and + // the other client took the lock instead of us. + if (FALSE === $result) { + // Explicit transaction release which also frees the WATCH'ed key. + $client->discard(); + return FALSE; + } + + // Register the lock. + return ($this->_locks[$name] = TRUE); + } + + return FALSE; + } + + public function lockMayBeAvailable($name) { + $client = $this->getClient(); + $key = $this->getKey($name); + $id = $this->getLockId(); + + $value = $client->get($key); + + return FALSE === $value || $id == $value; + } + + public function lockRelease($name) { + $client = $this->getClient(); + $key = $this->getKey($name); + $id = $this->getLockId(); + + unset($this->_locks[$name]); + + // Ensure the lock deletion is an atomic transaction. If another thread + // manages to removes all lock, we can not alter it anymore else we will + // release the lock for the other thread and cause race conditions. + $client->watch($key); + + if ($client->get($key) == $id) { + $client->multi(); + $client->delete($key); + $client->exec(); + } + else { + $client->unwatch(); + } + } + + public function lockReleaseAll($lock_id = NULL) { + if (!isset($lock_id) && empty($this->_locks)) { + return; + } + + $client = $this->getClient(); + $id = isset($lock_id) ? $lock_id : $this->getLockId(); + + // We can afford to deal with a slow algorithm here, this should not happen + // on normal run because we should have removed manually all our locks. + foreach (array_keys($this->_locks) as $name) { + $key = $this->getKey($name); + $owner = $client->get($key); + + if (empty($owner) || $owner == $id) { + $client->delete($key); + } + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php new file mode 100644 index 00000000..11904c17 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php @@ -0,0 +1,137 @@ +getClient(); + $key = $this->getKey($name); + $id = $this->getLockId(); + + // Insure that the timeout is at least 1 second, we cannot do otherwise with + // Redis, this is a minor change to the function signature, but in real life + // nobody will notice with so short duration. + $timeout = ceil(max($timeout, 1)); + + // If we already have the lock, check for his owner and attempt a new EXPIRE + // command on it. + if (isset($this->_locks[$name])) { + + // Create a new transaction, for atomicity. + $client->watch($key); + + // Global tells us we are the owner, but in real life it could have expired + // and another process could have taken it, check that. + if ($client->get($key) != $id) { + $client->unwatch(); + unset($this->_locks[$name]); + return FALSE; + } + + $replies = $client->pipeline(function($pipe) use ($key, $timeout, $id) { + $pipe->multi(); + $pipe->setex($key, $timeout, $id); + $pipe->exec(); + }); + + $execReply = array_pop($replies); + + if (FALSE === $execReply[0]) { + unset($this->_locks[$name]); + return FALSE; + } + + return TRUE; + } + else { + $client->watch($key); + $owner = $client->get($key); + + if (!empty($owner) && $owner != $id) { + $client->unwatch(); + unset($this->_locks[$name]); + return FALSE; + } + + $replies = $client->pipeline(function($pipe) use ($key, $timeout, $id) { + $pipe->multi(); + $pipe->setex($key, $timeout, $id); + $pipe->exec(); + }); + + $execReply = array_pop($replies); + + // If another client modified the $key value, transaction will be discarded + // $result will be set to FALSE. This means atomicity have been broken and + // the other client took the lock instead of us. + // EXPIRE and SETEX won't return something here, EXEC return is index 0 + // This was determined debugging, seems to be Predis specific. + if (FALSE === $execReply[0]) { + return FALSE; + } + + // Register the lock and return. + return ($this->_locks[$name] = TRUE); + } + + return FALSE; + } + + public function lockMayBeAvailable($name) { + $client = $this->getClient(); + $key = $this->getKey($name); + $id = $this->getLockId(); + + $value = $client->get($key); + + return empty($value) || $id == $value; + } + + public function lockRelease($name) { + $client = $this->getClient(); + $key = $this->getKey($name); + $id = $this->getLockId(); + + unset($this->_locks[$name]); + + // Ensure the lock deletion is an atomic transaction. If another thread + // manages to removes all lock, we can not alter it anymore else we will + // release the lock for the other thread and cause race conditions. + $client->watch($key); + + if ($client->get($key) == $id) { + $client->multi(); + $client->del(array($key)); + $client->exec(); + } + else { + $client->unwatch(); + } + } + + public function lockReleaseAll($lock_id = NULL) { + if (!isset($lock_id) && empty($this->_locks)) { + return; + } + + $client = $this->getClient(); + $id = isset($lock_id) ? $lock_id : $this->getLockId(); + + // We can afford to deal with a slow algorithm here, this should not happen + // on normal run because we should have removed manually all our locks. + foreach (array_keys($this->_locks) as $name) { + $key = $this->getKey($name); + $owner = $client->get($key); + + if (empty($owner) || $owner == $id) { + $client->del(array($key)); + } + } + } +} + diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Path/AbstractHashLookup.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Path/AbstractHashLookup.php new file mode 100644 index 00000000..4cdf578e --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Path/AbstractHashLookup.php @@ -0,0 +1,105 @@ +normalize($alias); + $source = $this->normalize($source); + + if (null === $language) { + $language = LANGUAGE_NONE; + } + + if (!empty($source)) { + $this->saveInHash($this->getKey(array(self::KEY_ALIAS, $language)), $source, $alias); + } + if (!empty($alias)) { + $this->saveInHash($this->getKey(array(self::KEY_SOURCE, $language)), $alias, $source); + } + } + + /** + * {@inheritdoc} + */ + public function deleteAlias($source, $alias, $language = null) + { + $alias = $this->normalize($alias); + $source = $this->normalize($source); + + if (null === $language) { + $language = LANGUAGE_NONE; + } + + $this->deleteInHash($this->getKey(array(self::KEY_ALIAS, $language)), $source, $alias); + $this->deleteInHash($this->getKey(array(self::KEY_SOURCE, $language)), $alias, $source); + } + + /** + * {@inheritdoc} + */ + public function lookupAlias($source, $language = null) + { + $source = $this->normalize($source); + + return $this->lookupInHash(self::KEY_ALIAS, $source, $language); + } + + /** + * {@inheritdoc} + */ + public function lookupSource($alias, $language = null) + { + $alias = $this->normalize($alias); + + return $this->lookupInHash(self::KEY_SOURCE, $alias, $language); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Path/HashLookupInterface.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Path/HashLookupInterface.php new file mode 100644 index 00000000..a1940a2a --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Path/HashLookupInterface.php @@ -0,0 +1,109 @@ +getClient(); + + $value = $client->hget($key, $hkey); + + if ($value === self::VALUE_NULL) { // Remove any null values + $value = null; + } + if ($value) { + $existing = explode(self::VALUE_SEPARATOR, $value); + if (!in_array($hvalue, $existing)) { + // Prepend the most recent path to ensure it always be + // first fetched one + // @todo Ensure in case of update that its position does + // not changes (pid ordering in Drupal core) + $value = $hvalue . self::VALUE_SEPARATOR . $value; + } else { // Do nothing on empty value + $value = null; + } + } else if (empty($hvalue)) { + $value = self::VALUE_NULL; + } else { + $value = $hvalue; + } + + if (!empty($value)) { + $client->hset($key, $hkey, $value); + } + // Empty value here means that we already got it + } + + protected function deleteInHash($key, $hkey, $hvalue) + { + $client = $this->getClient(); + + $value = $client->hget($key, $hkey); + + if ($value) { + $existing = explode(self::VALUE_SEPARATOR, $value); + if (false !== ($index = array_search($hvalue, $existing))) { + if (1 === count($existing)) { + $client->hdel($key, $hkey); + } else { + unset($existing[$index]); + $client->hset($key, $hkey, implode(self::VALUE_SEPARATOR, $existing)); + } + } + } + } + + protected function lookupInHash($keyPrefix, $hkey, $language = null) + { + $client = $this->getClient(); + + if (null === $language) { + $language = LANGUAGE_NONE; + $doNoneLookup = false; + } else if (LANGUAGE_NONE === $language) { + $doNoneLookup = false; + } else { + $doNoneLookup = true; + } + + $ret = $client->hget($this->getKey(array($keyPrefix, $language)), $hkey); + if ($doNoneLookup && (!$ret || self::VALUE_NULL === $ret)) { + $previous = $ret; + $ret = $client->hget($this->getKey(array($keyPrefix, LANGUAGE_NONE)), $hkey); + if (!$ret || self::VALUE_NULL === $ret) { + // Restore null placeholder else we loose conversion to false + // and drupal_lookup_path() would attempt saving it once again + $ret = $previous; + } + } + + if (self::VALUE_NULL === $ret) { + return false; // Needs conversion + } + if (empty($ret)) { + return null; // Value not found + } + + $existing = explode(self::VALUE_SEPARATOR, $ret); + + return reset($existing); + } + + /** + * {@inheritdoc} + */ + public function deleteLanguage($language) + { + $client = $this->getClient(); + $client->del($this->getKey(array(self::KEY_ALIAS, $language))); + $client->del($this->getKey(array(self::KEY_SOURCE, $language))); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php new file mode 100644 index 00000000..a0eb74d0 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php @@ -0,0 +1,108 @@ +getClient(); + + $value = $client->hget($key, $hkey); + + if ($value === self::VALUE_NULL) { // Remove any null values + $value = null; + } + if ($value) { + $existing = explode(self::VALUE_SEPARATOR, $value); + if (!in_array($hvalue, $existing)) { + // Prepend the most recent path to ensure it always be + // first fetched one + // @todo Ensure in case of update that its position does + // not changes (pid ordering in Drupal core) + $value = $hvalue . self::VALUE_SEPARATOR . $value; + } else { // Do nothing on empty value + $value = null; + } + } else if (empty($hvalue)) { + $value = self::VALUE_NULL; + } else { + $value = $hvalue; + } + + if (!empty($value)) { + $client->hset($key, $hkey, $value); + } + // Empty value here means that we already got it + } + + protected function deleteInHash($key, $hkey, $hvalue) + { + $client = $this->getClient(); + + $value = $client->hget($key, $hkey); + + if ($value) { + $existing = explode(self::VALUE_SEPARATOR, $value); + if (false !== ($index = array_search($hvalue, $existing))) { + if (1 === count($existing)) { + $client->hdel($key, $hkey); + } else { + unset($existing[$index]); + $client->hset($key, $hkey, implode(self::VALUE_SEPARATOR, $existing)); + } + } + } + } + + protected function lookupInHash($keyPrefix, $hkey, $language = null) + { + $client = $this->getClient(); + + if (null === $language) { + $language = LANGUAGE_NONE; + $doNoneLookup = false; + } else if (LANGUAGE_NONE === $language) { + $doNoneLookup = false; + } else { + $doNoneLookup = true; + } + + $ret = $client->hget($this->getKey(array($keyPrefix, $language)), $hkey); + if ($doNoneLookup && (!$ret || self::VALUE_NULL === $ret)) { + $previous = $ret; + $ret = $client->hget($this->getKey(array($keyPrefix, LANGUAGE_NONE)), $hkey); + if (!$ret || self::VALUE_NULL === $ret) { + // Restore null placeholder else we loose conversion to false + // and drupal_lookup_path() would attempt saving it once again + $ret = $previous; + } + } + + if (self::VALUE_NULL === $ret) { + return false; // Needs conversion + } + if (empty($ret)) { + return null; // Value not found + } + + $existing = explode(self::VALUE_SEPARATOR, $ret); + + return reset($existing); + } + + /** + * {@inheritdoc} + */ + public function deleteLanguage($language) + { + $client = $this->getClient(); + $client->del($this->getKey(array(self::KEY_ALIAS, $language))); + $client->del($this->getKey(array(self::KEY_SOURCE, $language))); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php new file mode 100644 index 00000000..1d9e48fe --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php @@ -0,0 +1,58 @@ +backend = new $className(Redis_Client::getClient(), $name); + } + + public function createItem($data) + { + return $this->backend->createItem($data); + } + + public function numberOfItems() + { + return $this->backend->numberOfItems(); + } + + public function claimItem($lease_time = 3600) + { + return $this->backend->claimItem($lease_time); + } + + public function deleteItem($item) + { + return $this->backend->deleteItem($item); + } + + public function releaseItem($item) + { + return $this->backend->releaseItem($item); + } + + public function createQueue() + { + return $this->backend->createQueue(); + } + + public function deleteQueue() + { + return $this->backend->deleteQueue(); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php new file mode 100644 index 00000000..985a96fc --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php @@ -0,0 +1,99 @@ +getKey('data'); + } + + /** + * Get queued items LIST key + * + * Key will already be prefixed + * + * @return string + */ + public function getKeyForQueue() + { + return $this->getKey('queued'); + } + + /** + * Get claimed LIST key + * + * Key will already be prefixed + * + * @return string + */ + public function getKeyForClaimed() + { + return $this->getKey('claimed'); + } + + /** + * Default contructor + * + * Beware that DrupalQueueInterface does not defines the __construct + * method in the interface yet is being used from DrupalQueue::get() + * + * @param mixed $client + * @param string $name + */ + public function __construct($client, $name) + { + parent::__construct($client, self::QUEUE_KEY_PREFIX . $name); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php new file mode 100644 index 00000000..e3d590dd --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php @@ -0,0 +1,106 @@ +getClient(); + + $dKey = $this->getKeyForData(); + $qKey = $this->getKeyForQueue(); + + // Identifier does not not need to be in the transaction, + // in case of any error we'll just skip a value in the sequence. + $id = $client->hincrby($dKey, self::QUEUE_HKEY_SEQ, 1); + + $record = new stdClass(); + $record->qid = $id; + $record->data = $data; + $record->timestamp = time(); + + $pipe = $client->multi(Redis::PIPELINE); + // Thanks to the redis_queue standalone module maintainers for + // this piece of code, very effective. Note that we added the + // pipeline thought. + $pipe->hsetnx($dKey, $id, serialize($record)); + $pipe->llen($qKey); + $pipe->lpush($qKey, $id); + $ret = $pipe->exec(); + + if (!$success = ($ret[0] && $ret[1] < $ret[2])) { + if ($ret[0]) { + // HSETNEX worked but not the PUSH command we therefore + // need to drop the inserted data. I would have prefered + // a DISCARD instead but we are in pipelined transaction + // we cannot actually do a DISCARD here. + $client->hdel($dKey, $id); + } + } + + return $success; + } + + public function numberOfItems() + { + return $this->getClient()->llen($this->getKeyForQueue()); + } + + public function claimItem($lease_time = 30) + { + // @todo Deal with lease + $client = $this->getClient(); + + $id = $client->rpoplpush( + $this->getKeyForQueue(), + $this->getKeyForClaimed() + ); + + if ($id) { + if ($item = $client->hget($this->getKeyForData(), $id)) { + if ($item = unserialize($item)) { + return $item; + } + } + } + + return false; + } + + public function deleteItem($item) + { + $pipe = $this->getClient()->multi(Redis::PIPELINE); + $pipe->lrem($this->getKeyForQueue(), $item->qid); + $pipe->lrem($this->getKeyForClaimed(), $item->qid); + $pipe->hdel($this->getKeyForData(), $item->qid); + $pipe->exec(); + } + + public function releaseItem($item) + { + $pipe = $this->getClient()->multi(Redis::PIPELINE); + $pipe->lrem($this->getKeyForClaimed(), $item->qid, -1); + $pipe->lpush($this->getKeyForQueue(), $item->qid); + $ret = $pipe->exec(); + + return $ret[0] && $ret[1]; + } + + public function createQueue() + { + } + + public function deleteQueue() + { + $this->getClient()->del( + $this->getKeyForQueue(), + $this->getKeyForClaimed(), + $this->getKeyForData() + ); + } +} + diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/AbstractUnitTestCase.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/AbstractUnitTestCase.php new file mode 100644 index 00000000..b23f4cdc --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/AbstractUnitTestCase.php @@ -0,0 +1,141 @@ + null, + 'cache_prefix' => null, + 'redis_client_interface' => null, + 'redis_eval_enabled' => null, + 'redis_flush_mode' => null, + 'redis_perm_ttl' => null, + ); + + /** + * Prepare Drupal environmment for testing + */ + final private function prepareDrupalEnvironment() + { + // Site on which the tests are running may define this variable + // in their own settings.php file case in which it will be merged + // with testing site + global $conf; + foreach (array_keys($this->originalConf) as $key) { + if (isset($conf[$key])) { + $this->originalConf[$key] = $conf[$key]; + unset($conf[$key]); + } + } + $conf['cache_prefix'] = $this->testId; + } + + /** + * Restore Drupal environment after testing. + */ + final private function restoreDrupalEnvironment() + { + $GLOBALS['conf'] = $this->originalConf + $GLOBALS['conf']; + } + + /** + * Prepare client manager + */ + final private function prepareClientManager() + { + $interface = $this->getClientInterface(); + + if (null === $interface) { + throw new \Exception("Test skipped due to missing driver"); + } + + $GLOBALS['conf']['redis_client_interface'] = $interface; + Redis_Client::reset(); + } + + /** + * Restore client manager + */ + final private function restoreClientManager() + { + Redis_Client::reset(); + } + + /** + * Set up the Redis configuration. + * + * Set up the needed variables using variable_set() if necessary. + * + * @return string + * Client interface or null if not exists + */ + abstract protected function getClientInterface(); + + /** + * {@inheritdoc} + */ + public function setUp() + { + self::enableAutoload(); + + $this->prepareDrupalEnvironment(); + $this->prepareClientManager(); + + parent::setUp(); + + drupal_install_schema('system'); + drupal_install_schema('locale'); + } + + /** + * {@inheritdoc} + */ + public function tearDown() + { + drupal_uninstall_schema('locale'); + drupal_uninstall_schema('system'); + + $this->restoreDrupalEnvironment(); + $this->restoreClientManager(); + + parent::tearDown(); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Admin/VariableTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Admin/VariableTestCase.test new file mode 100644 index 00000000..7e19eec9 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Admin/VariableTestCase.test @@ -0,0 +1,60 @@ + 'Redis variables', + 'description' => 'Checks that Redis module variables are correctly type hinted when saved.', + 'group' => 'Redis', + ); + } + + protected $adminUser; + + public function setUp() + { + parent::setUp('redis'); + } + + public function testSave() + { + $this->adminUser = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($this->adminUser); + + // Tests port is an int. + $this->drupalGet('admin/config/development/performance/redis'); + $edit = array( + 'redis_client_base' => '', + 'redis_client_port' => '1234', + 'redis_client_host' => 'localhost', + 'redis_client_interface' => '', + ); + $this->drupalPost('admin/config/development/performance/redis', $edit, t('Save configuration')); + + // Force variable cache to refresh. + $test = variable_initialize(); + $conf = &$GLOBALS['conf']; + + $this->assertFalse(array_key_exists('redis_client_base', $conf), "Empty int value has been removed"); + $this->assertFalse(array_key_exists('redis_client_interface', $conf), "Empty string value has been removed"); + $this->assertIdentical($conf['redis_client_port'], 1234, "Saved int is an int"); + $this->assertIdentical($conf['redis_client_host'], 'localhost', "Saved string is a string"); + + $this->drupalGet('admin/config/development/performance/redis'); + $edit = array( + 'redis_client_base' => '0', + 'redis_client_port' => '1234', + 'redis_client_host' => 'localhost', + 'redis_client_interface' => '', + ); + $this->drupalPost('admin/config/development/performance/redis', $edit, t('Save configuration')); + + // Force variable cache to refresh. + $test = variable_initialize(); + $conf = &$GLOBALS['conf']; + + $this->assertIdentical($conf['redis_client_base'], 0, "Saved 0 valueed int is an int"); + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFixesUnitTestCase.test new file mode 100644 index 00000000..5555dbb6 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFixesUnitTestCase.test @@ -0,0 +1,27 @@ + 'Compressed PhpRedis cache fixes', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function createCacheInstance($name = null) + { + return new Redis_CacheCompressed($name); + } + + protected function getClientInterface() + { + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFlushUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFlushUnitTestCase.test new file mode 100644 index 00000000..f901bf3f --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFlushUnitTestCase.test @@ -0,0 +1,27 @@ + 'Compressed PhpRedis cache flush', + 'description' => 'Tests Redis module cache flush modes feature.', + 'group' => 'Redis', + ); + } + + protected function createCacheInstance($name = null) + { + return new Redis_CacheCompressed($name); + } + + protected function getClientInterface() + { + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFixesUnitTestCase.test new file mode 100644 index 00000000..8369fec1 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFixesUnitTestCase.test @@ -0,0 +1,25 @@ + 'Compressed PhpRedis cache fixes (S)', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function createCacheInstance($name = null) + { + return new Redis_CacheCompressed($name); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD; + + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFlushUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFlushUnitTestCase.test new file mode 100644 index 00000000..e8b973b3 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFlushUnitTestCase.test @@ -0,0 +1,25 @@ + 'Compressed PhpRedis cache flush (S)', + 'description' => 'Tests Redis module cache flush modes feature.', + 'group' => 'Redis', + ); + } + + protected function createCacheInstance($name = null) + { + return new Redis_CacheCompressed($name); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD; + + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedWithPipelineFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedWithPipelineFixesUnitTestCase.test new file mode 100644 index 00000000..06994472 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedWithPipelineFixesUnitTestCase.test @@ -0,0 +1,25 @@ + 'Compressed PhpRedis cache fixes (SP)', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function createCacheInstance($name = null) + { + return new Redis_CacheCompressed($name); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING; + + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FixesUnitTestCase.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FixesUnitTestCase.php new file mode 100644 index 00000000..51200516 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FixesUnitTestCase.php @@ -0,0 +1,209 @@ +createCacheInstance($name); + + $this->assert(true, "Redis client is " . ($backend->isSharded() ? '' : "NOT ") . " sharded"); + $this->assert(true, "Redis client is " . ($backend->allowTemporaryFlush() ? '' : "NOT ") . " allowed to flush temporary entries"); + $this->assert(true, "Redis client is " . ($backend->allowPipeline() ? '' : "NOT ") . " allowed to use pipeline"); + + return $backend; + } + + public function testTemporaryCacheExpire() + { + global $conf; // We are in unit tests so variable table does not exist. + + $backend = $this->getBackend(); + + // Permanent entry. + $backend->set('test1', 'foo', CACHE_PERMANENT); + $data = $backend->get('test1'); + $this->assertNotEqual(false, $data); + $this->assertIdentical('foo', $data->data); + + // Permanent entries should not be dropped on clear() call. + $backend->clear(); + $data = $backend->get('test1'); + $this->assertNotEqual(false, $data); + $this->assertIdentical('foo', $data->data); + + // Expiring entry with permanent default lifetime. + $conf['cache_lifetime'] = 0; + $backend->set('test2', 'bar', CACHE_TEMPORARY); + sleep(2); + $data = $backend->get('test2'); + $this->assertNotEqual(false, $data); + $this->assertIdentical('bar', $data->data); + sleep(2); + $data = $backend->get('test2'); + $this->assertNotEqual(false, $data); + $this->assertIdentical('bar', $data->data); + + // Expiring entry with negative lifetime. + $backend->set('test3', 'baz', time() - 100); + $data = $backend->get('test3'); + $this->assertEqual(false, $data); + + // Expiring entry with short lifetime. + $backend->set('test4', 'foobar', time() + 2); + $data = $backend->get('test4'); + $this->assertNotEqual(false, $data); + $this->assertIdentical('foobar', $data->data); + sleep(4); + $data = $backend->get('test4'); + $this->assertEqual(false, $data); + + // Expiring entry with short default lifetime. + $conf['cache_lifetime'] = 1; + $backend->refreshMaxTtl(); + $backend->set('test5', 'foobaz', CACHE_TEMPORARY); + $data = $backend->get('test5'); + $this->assertNotEqual(false, $data); + $this->assertIdentical('foobaz', $data->data); + sleep(3); + $data = $backend->get('test5'); + $this->assertEqual(false, $data); + } + + public function testDefaultPermTtl() + { + global $conf; + unset($conf['redis_perm_ttl']); + $backend = $this->getBackend(); + $this->assertIdentical(Redis_Cache::LIFETIME_PERM_DEFAULT, $backend->getPermTtl()); + } + + public function testUserSetDefaultPermTtl() + { + global $conf; + // This also testes string parsing. Not fully, but at least one case. + $conf['redis_perm_ttl'] = "3 months"; + $backend = $this->getBackend(); + $this->assertIdentical(7776000, $backend->getPermTtl()); + } + + public function testUserSetPermTtl() + { + global $conf; + // This also testes string parsing. Not fully, but at least one case. + $conf['redis_perm_ttl'] = "1 months"; + $backend = $this->getBackend(); + $this->assertIdentical(2592000, $backend->getPermTtl()); + } + + public function testGetMultiple() + { + $backend = $this->getBackend(); + + $backend->set('multiple1', 1); + $backend->set('multiple2', 2); + $backend->set('multiple3', 3); + $backend->set('multiple4', 4); + + $cidList = array('multiple1', 'multiple2', 'multiple3', 'multiple4', 'multiple5'); + $ret = $backend->getMultiple($cidList); + + $this->assertEqual(1, count($cidList)); + $this->assertFalse(isset($cidList[0])); + $this->assertFalse(isset($cidList[1])); + $this->assertFalse(isset($cidList[2])); + $this->assertFalse(isset($cidList[3])); + $this->assertTrue(isset($cidList[4])); + + $this->assertEqual(4, count($ret)); + $this->assertTrue(isset($ret['multiple1'])); + $this->assertTrue(isset($ret['multiple2'])); + $this->assertTrue(isset($ret['multiple3'])); + $this->assertTrue(isset($ret['multiple4'])); + $this->assertFalse(isset($ret['multiple5'])); + } + + public function testPermTtl() + { + global $conf; + // This also testes string parsing. Not fully, but at least one case. + $conf['redis_perm_ttl'] = "2 seconds"; + $backend = $this->getBackend(); + $this->assertIdentical(2, $backend->getPermTtl()); + + $backend->set('test6', 'cats are mean'); + $this->assertIdentical('cats are mean', $backend->get('test6')->data); + + sleep(3); + $item = $backend->get('test6'); + $this->assertTrue(empty($item)); + } + + public function testClearAsArray() + { + $backend = $this->getBackend(); + + $backend->set('test7', 1); + $backend->set('test8', 2); + $backend->set('test9', 3); + + $backend->clear(array('test7', 'test9')); + + $item = $backend->get('test7'); + $this->assertTrue(empty($item)); + $item = $backend->get('test8'); + $this->assertEqual(2, $item->data); + $item = $backend->get('test9'); + $this->assertTrue(empty($item)); + } + + public function testGetMultipleAlterCidsWhenCacheHitsOnly() + { + $backend = $this->getBackend(); + $backend->clear('*', true); // It seems that there are leftovers. + + $backend->set('mtest1', 'pouf'); + + $cids_partial_hit = array('foo' => 'mtest1', 'bar' => 'mtest2'); + $entries = $backend->getMultiple($cids_partial_hit); + $this->assertIdentical(1, count($entries)); + // Note that the key is important because the method should + // keep the keys synchronized. + $this->assertEqual(array('bar' => 'mtest2'), $cids_partial_hit); + + $backend->clear('mtest1'); + + $cids_no_hit = array('cat' => 'mtest1', 'dog' => 'mtest2'); + $entries = $backend->getMultiple($cids_no_hit); + $this->assertIdentical(0, count($entries)); + $this->assertEqual(array('cat' => 'mtest1', 'dog' => 'mtest2'), $cids_no_hit); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FlushUnitTestCase.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FlushUnitTestCase.php new file mode 100644 index 00000000..41f519a7 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FlushUnitTestCase.php @@ -0,0 +1,185 @@ +createCacheInstance($name); + + $this->assert(true, "Redis client is " . ($backend->isSharded() ? '' : "NOT ") . " sharded"); + $this->assert(true, "Redis client is " . ($backend->allowTemporaryFlush() ? '' : "NOT ") . " allowed to flush temporary entries"); + $this->assert(true, "Redis client is " . ($backend->allowPipeline() ? '' : "NOT ") . " allowed to use pipeline"); + + return $backend; + } + + /** + * Tests that with a default cache lifetime temporary non expired + * items are kept even when in temporary flush mode. + */ + public function testFlushIsTemporaryWithLifetime() + { + $GLOBALS['conf']['cache_lifetime'] = 112; + + $backend = $this->getBackend(); + + // Even though we set a flush mode into this bin, Drupal default + // behavior when a cache_lifetime is set is to override the backend + // one in order to keep the core behavior and avoid potential + // nasty bugs. + $this->assertFalse($backend->allowTemporaryFlush()); + + $backend->set('test7', 42, CACHE_PERMANENT); + $backend->set('test8', 'foo', CACHE_TEMPORARY); + $backend->set('test9', 'bar', time() + 1000); + + $backend->clear(); + + $cache = $backend->get('test7'); + $this->assertNotEqual(false, $cache); + $this->assertEqual($cache->data, 42); + $cache = $backend->get('test8'); + $this->assertNotEqual(false, $cache); + $this->assertEqual($cache->data, 'foo'); + $cache = $backend->get('test9'); + $this->assertNotEqual(false, $cache); + $this->assertEqual($cache->data, 'bar'); + } + + /** + * Tests that with no default cache lifetime all temporary items are + * droppped when in temporary flush mode. + */ + public function testFlushIsTemporaryWithoutLifetime() + { + $backend = $this->getBackend(); + + $this->assertTrue($backend->allowTemporaryFlush()); + + $backend->set('test10', 42, CACHE_PERMANENT); + // Ugly concatenation with the mode, but it will be visible in tests + // reports if the entry shows up, thus allowing us to know which real + // test case is run at this time + $backend->set('test11', 'foo' . $backend->isSharded(), CACHE_TEMPORARY); + $backend->set('test12', 'bar' . $backend->isSharded(), time() + 10); + + $backend->clear(); + + $cache = $backend->get('test10'); + $this->assertNotEqual(false, $cache); + $this->assertEqual($cache->data, 42); + $this->assertFalse($backend->get('test11')); + + $cache = $backend->get('test12'); + $this->assertNotEqual(false, $cache); + } + + public function testNormalFlushing() + { + $backend = $this->getBackend(); + $backendUntouched = $this->getBackend(); + + // Set a few entries. + $backend->set('test13', 'foo'); + $backend->set('test14', 'bar', CACHE_TEMPORARY); + $backend->set('test15', 'baz', time() + 3); + + $backendUntouched->set('test16', 'dog'); + $backendUntouched->set('test17', 'cat', CACHE_TEMPORARY); + $backendUntouched->set('test18', 'xor', time() + 5); + + // This should not do anything (bugguy command) + $backend->clear('', true); + $backend->clear('', false); + $this->assertNotIdentical(false, $backend->get('test13')); + $this->assertNotIdentical(false, $backend->get('test14')); + $this->assertNotIdentical(false, $backend->get('test15')); + $this->assertNotIdentical(false, $backendUntouched->get('test16')); + $this->assertNotIdentical(false, $backendUntouched->get('test17')); + $this->assertNotIdentical(false, $backendUntouched->get('test18')); + + // This should clear every one, permanent and volatile + $backend->clear('*', true); + $this->assertFalse($backend->get('test13')); + $this->assertFalse($backend->get('test14')); + $this->assertFalse($backend->get('test15')); + $this->assertNotIdentical(false, $backendUntouched->get('test16')); + $this->assertNotIdentical(false, $backendUntouched->get('test17')); + $this->assertNotIdentical(false, $backendUntouched->get('test18')); + } + + public function testPrefixDeletionWithSeparatorChar() + { + $backend = $this->getBackend(); + + $backend->set('testprefix10', 'foo'); + $backend->set('testprefix11', 'foo'); + $backend->set('testprefix:12', 'bar'); + $backend->set('testprefix:13', 'baz'); + $backend->set('testnoprefix14', 'giraffe'); + $backend->set('testnoprefix:15', 'elephant'); + + $backend->clear('testprefix:', true); + $this->assertFalse($backend->get('testprefix:12')); + $this->assertFalse($backend->get('testprefix:13')); + // @todo Temporary fix + // At the moment shard enabled backends will erase all data instead + // of just removing by prefix, so those tests won't pass + if (!$backend->isSharded()) { + $this->assertNotIdentical(false, $backend->get('testprefix10')); + $this->assertNotIdentical(false, $backend->get('testprefix11')); + $this->assertNotIdentical(false, $backend->get('testnoprefix14')); + $this->assertNotIdentical(false, $backend->get('testnoprefix:15')); + } + + $backend->clear('testprefix', true); + $this->assertFalse($backend->get('testprefix10')); + $this->assertFalse($backend->get('testprefix11')); + // @todo Temporary fix + // At the moment shard enabled backends will erase all data instead + // of just removing by prefix, so those tests won't pass + if (!$backend->isSharded()) { + $this->assertNotIdentical(false, $backend->get('testnoprefix14')); + $this->assertNotIdentical(false, $backend->get('testnoprefix:15')); + } + } + + public function testOrder() + { + $backend = $this->getBackend(); + + for ($i = 0; $i < 10; ++$i) { + $id = 'speedtest' . $i; + $backend->set($id, 'somevalue'); + $this->assertNotIdentical(false, $backend->get($id)); + $backend->clear('*', true); + // Value created the same second before is dropped + $this->assertFalse($backend->get($id)); + $backend->set($id, 'somevalue'); + // Value created the same second after is kept + $this->assertNotIdentical(false, $backend->get($id)); + } + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFixesUnitTestCase.test new file mode 100644 index 00000000..a2ad1369 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFixesUnitTestCase.test @@ -0,0 +1,22 @@ + 'PhpRedis cache fixes', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFlushUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFlushUnitTestCase.test new file mode 100644 index 00000000..d4c215c1 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFlushUnitTestCase.test @@ -0,0 +1,22 @@ + 'PhpRedis cache flush', + 'description' => 'Tests Redis module cache flush modes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFixesUnitTestCase.test new file mode 100644 index 00000000..1a75d1b7 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFixesUnitTestCase.test @@ -0,0 +1,20 @@ + 'PhpRedis cache fixes (S)', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD; + + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFlushUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFlushUnitTestCase.test new file mode 100644 index 00000000..034b13ff --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFlushUnitTestCase.test @@ -0,0 +1,20 @@ + 'PhpRedis cache flush (S)', + 'description' => 'Tests Redis module cache flush modes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD; + + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedWithPipelineFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedWithPipelineFixesUnitTestCase.test new file mode 100644 index 00000000..38e6f7cc --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedWithPipelineFixesUnitTestCase.test @@ -0,0 +1,20 @@ + 'PhpRedis cache fixes (SP)', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING; + + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFixesUnitTestCase.test new file mode 100644 index 00000000..0880dfd7 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFixesUnitTestCase.test @@ -0,0 +1,18 @@ + 'Predis cache fixes', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'Predis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFlushUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFlushUnitTestCase.test new file mode 100644 index 00000000..bc94f8e1 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFlushUnitTestCase.test @@ -0,0 +1,18 @@ + 'Predis cache flush', + 'description' => 'Tests Redis module cache flush modes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'Predis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFixesUnitTestCase.test new file mode 100644 index 00000000..f91c8561 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFixesUnitTestCase.test @@ -0,0 +1,20 @@ + 'Predis cache fixes (S)', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD; + + return 'Predis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFlushUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFlushUnitTestCase.test new file mode 100644 index 00000000..62867d72 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFlushUnitTestCase.test @@ -0,0 +1,20 @@ + 'Predis cache flush (S)', + 'description' => 'Tests Redis module cache flush modes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD; + + return 'Predis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedWithPipelineFixesUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedWithPipelineFixesUnitTestCase.test new file mode 100644 index 00000000..5f8938cc --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedWithPipelineFixesUnitTestCase.test @@ -0,0 +1,20 @@ + 'Predis cache fixes (SP)', + 'description' => 'Tests Redis module cache fixes feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + $GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING; + + return 'Predis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/ClientUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/ClientUnitTestCase.test new file mode 100644 index 00000000..e64d7f74 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/ClientUnitTestCase.test @@ -0,0 +1,66 @@ + 'Redis client manager', + 'description' => 'Tests Redis module client manager feature.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'PhpRedis'; + } + + public function getManager() + { + return new Redis_Client_Manager( + new Redis_Tests_Client_MockFactory(), + array( + 'default' => array(), + 'foo' => array( + 'host' => 'foo.com', + 'port' => 666, + ), + 'bar' => array( + 'host' => 'bar.com', + ), + ) + ); + } + + public function testManagerServerList() + { + $manager = $this->getManager(); + + $defaultClient = $manager->getClient(); + $this->assertTrue(is_object($defaultClient)); + + // Ensure defaults are OK + $this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_HOST, $defaultClient->host); + $this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_PORT, $defaultClient->port); + $this->assertFalse(property_exists($defaultClient, 'base')); + $this->assertFalse(property_exists($defaultClient, 'password')); + + $client = $manager->getClient('foo'); + $this->assertIdentical('foo.com', $client->host); + $this->assertIdentical(666, $client->port); + + $client = $manager->getClient('bar'); + $this->assertIdentical('bar.com', $client->host); + $this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_PORT, $client->port); + + $this->assertIdentical($defaultClient, $manager->getClient('non_existing')); + + try { + $manager->getClient('other_non_existing', false); + $this->assert(false); + } catch (\InvalidArgumentException $e) { + $this->assert(true); + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/MockFactory.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/MockFactory.php new file mode 100644 index 00000000..0f86baa2 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/MockFactory.php @@ -0,0 +1,14 @@ +backends)) { + foreach ($this->backends as $backend) { + $backend->lockReleaseAll(); + } + + $this->backends = array(); + } + + parent::tearDown(); + } + + /** + * Create a new lock backend with a generated lock id + * + * @return Redis_Lock_BackendInterface + */ + public function createLockBackend() + { + if (!$this->getLockBackendClass()) { + throw new \Exception("Lock backend class does not exist"); + } + + $className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_LOCK); + + return $this->backends[] = new $className( + Redis_Client::getClient(), + Redis_Client::getDefaultPrefix('lock') + ); + } + + public function testLock() + { + $b1 = $this->createLockBackend(); + $b2 = $this->createLockBackend(); + + $s = $b1->lockAcquire('test1', 20000); + $this->assertTrue($s, "Lock test1 acquired"); + + $s = $b1->lockAcquire('test1', 20000); + $this->assertTrue($s, "Lock test1 acquired a second time by the same thread"); + + $s = $b2->lockAcquire('test1', 20000); + $this->assertFalse($s, "Lock test1 could not be acquired by another thread"); + + $b2->lockRelease('test1'); + $s = $b2->lockAcquire('test1'); + $this->assertFalse($s, "Lock test1 could not be released by another thread"); + + $b1->lockRelease('test1'); + $s = $b2->lockAcquire('test1'); + $this->assertTrue($s, "Lock test1 has been released by the first thread"); + } + + public function testReleaseAll() + { + $b1 = $this->createLockBackend(); + $b2 = $this->createLockBackend(); + + $b1->lockAcquire('test1', 200); + $b1->lockAcquire('test2', 2000); + $b1->lockAcquire('test3', 20000); + + $s = $b2->lockAcquire('test2'); + $this->assertFalse($s, "Lock test2 could not be released by another thread"); + $s = $b2->lockAcquire('test3'); + $this->assertFalse($s, "Lock test4 could not be released by another thread"); + + $b1->lockReleaseAll(); + + $s = $b2->lockAcquire('test1'); + $this->assertTrue($s, "Lock test1 has been released"); + $s = $b2->lockAcquire('test2'); + $this->assertTrue($s, "Lock test2 has been released"); + $s = $b2->lockAcquire('test3'); + $this->assertTrue($s, "Lock test3 has been released"); + + $b2->lockReleaseAll(); + } + + public function testConcurentLock() + { + /* + * Code for web test case + * + $this->drupalGet('redis/acquire/test1/1000'); + $this->assertText("REDIS_ACQUIRED", "Lock test1 acquired"); + + $this->drupalGet('redis/acquire/test1/1'); + $this->assertText("REDIS_FAILED", "Lock test1 could not be acquired by a second thread"); + + $this->drupalGet('redis/acquire/test2/1000'); + $this->assertText("REDIS_ACQUIRED", "Lock test2 acquired"); + + $this->drupalGet('redis/acquire/test2/1'); + $this->assertText("REDIS_FAILED", "Lock test2 could not be acquired by a second thread"); + */ + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PhpRedisLockingUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PhpRedisLockingUnitTestCase.test new file mode 100644 index 00000000..f00fff58 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PhpRedisLockingUnitTestCase.test @@ -0,0 +1,27 @@ + 'PhpRedis Redis locking', + 'description' => 'Ensure that Redis locking feature is working OK.', + 'group' => 'Redis', + ); + } + + protected function getLockBackendClass() + { + return 'Redis_Lock_PhpRedis'; + } + + protected function getClientInterface() + { + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PredisLockingUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PredisLockingUnitTestCase.test new file mode 100644 index 00000000..6cffbc14 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PredisLockingUnitTestCase.test @@ -0,0 +1,23 @@ + 'Predis Redis locking', + 'description' => 'Ensure that Redis locking feature is working OK.', + 'group' => 'Redis', + ); + } + + protected function getLockBackendClass() + { + return 'Redis_Lock_Predis'; + } + + protected function getClientInterface() + { + return 'Predis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PathUnitTestCase.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PathUnitTestCase.php new file mode 100644 index 00000000..91636f05 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PathUnitTestCase.php @@ -0,0 +1,148 @@ +getBackend(); + + $source = $backend->lookupSource('node-1-fr', 'fr'); + $this->assertIdentical(null, $source); + $alias = $backend->lookupAlias('node/1', 'fr'); + $this->assertIdentical(null, $source); + + $backend->saveAlias('node/1', 'node-1-fr', 'fr'); + $source = $backend->lookupSource('node-1-fr', 'fr'); + $source = $backend->lookupSource('node-1-fr', 'fr'); + $this->assertIdentical('node/1', $source); + $alias = $backend->lookupAlias('node/1', 'fr'); + $this->assertIdentical('node-1-fr', $alias); + + // Delete and ensure it does not exist anymore. + $backend->deleteAlias('node/1', 'node-1-fr', 'fr'); + $source = $backend->lookupSource('node-1-fr', 'fr'); + $this->assertIdentical(null, $source); + $alias = $backend->lookupAlias('node/1', 'fr'); + $this->assertIdentical(null, $source); + + // Set more than one aliases and ensure order at loading. + $backend->saveAlias('node/1', 'node-1-fr-1', 'fr'); + $backend->saveAlias('node/1', 'node-1-fr-2', 'fr'); + $backend->saveAlias('node/1', 'node-1-fr-3', 'fr'); + $alias = $backend->lookupAlias('node/1', 'fr'); + $this->assertIdentical('node-1-fr-3', $alias); + + // Add another alias to test the delete language feature. + // Also add some other languages aliases. + $backend->saveAlias('node/1', 'node-1'); + $backend->saveAlias('node/2', 'node-2-en', 'en'); + $backend->saveAlias('node/3', 'node-3-ca', 'ca'); + + // Ok, delete fr and tests every other are still there. + $backend->deleteLanguage('fr'); + $alias = $backend->lookupAlias('node/1'); + $this->assertIdentical('node-1', $alias); + $alias = $backend->lookupAlias('node/2', 'en'); + $this->assertIdentical('node-2-en', $alias); + $alias = $backend->lookupAlias('node/3', 'ca'); + $this->assertIdentical('node-3-ca', $alias); + + // Now create back a few entries in some langage and + // ensure fallback to no language also works. + $backend->saveAlias('node/4', 'node-4'); + $backend->saveAlias('node/4', 'node-4-es', 'es'); + $alias = $backend->lookupAlias('node/4'); + $this->assertIdentical('node-4', $alias); + $alias = $backend->lookupAlias('node/4', 'es'); + $this->assertIdentical('node-4-es', $alias); + $alias = $backend->lookupAlias('node/4', 'fr'); + $this->assertIdentical('node-4', $alias); + } + + /** + * Tests https://www.drupal.org/node/2728831 + */ + public function testSomeEdgeCaseFalseNegative() + { + $backend = $this->getBackend(); + + $backend->deleteLanguage('fr'); + $backend->deleteLanguage('und'); + $backend->saveAlias('node/123', 'node-123'); + + // Language lookup should return the language neutral value if no value + $source = $backend->lookupSource('node-123', 'fr'); + $this->assertIdentical($source, 'node/123'); + $source = $backend->lookupAlias('node/123', 'fr'); + $this->assertIdentical($source, 'node-123'); + + // Now, let's consider we have an item we don't know if it exists or + // not, per definition we should not return a strict FALSE but a NULL + // value instead to tell "we don't know anything about this". In a + // very specific use-case, if the language neutral value is a strict + // "not exists" value, it should still return NULL instead of FALSE + // if another language was asked for. + + // Store "value null" for the language neutral entry + $backend->saveAlias('node/456', Redis_Path_HashLookupInterface::VALUE_NULL); + $source = $backend->lookupAlias('node/456'); + $this->assertIdentical(false, $source); + + $source = $backend->lookupAlias('node/456', 'fr'); + $this->assertIdentical(null, $source); + } + + /** + * Tests that lookup is case insensitive + */ + public function testCaseInsensitivePathLookup() + { + $backend = $this->getBackend(); + + $backend->saveAlias('node/1', 'Node-1-FR', 'fr'); + $source = $backend->lookupSource('NODE-1-fr', 'fr'); + $this->assertIdentical('node/1', $source); + $source = $backend->lookupSource('node-1-FR', 'fr'); + $this->assertIdentical('node/1', $source); + $alias = $backend->lookupAlias('node/1', 'fr'); + $this->assertIdentical('node-1-fr', strtolower($alias)); + + // Delete and ensure it does not exist anymore. + $backend->deleteAlias('node/1', 'node-1-FR', 'fr'); + $source = $backend->lookupSource('Node-1-FR', 'fr'); + $this->assertIdentical(null, $source); + $alias = $backend->lookupAlias('node/1', 'fr'); + $this->assertIdentical(null, $source); + } +} diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PhpRedisPathUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PhpRedisPathUnitTestCase.test new file mode 100644 index 00000000..f18a61b6 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PhpRedisPathUnitTestCase.test @@ -0,0 +1,22 @@ + 'PhpRedis path inc replacement', + 'description' => 'Tests PhpRedis path inc replacement.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PredisPathUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PredisPathUnitTestCase.test new file mode 100644 index 00000000..62e501fe --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PredisPathUnitTestCase.test @@ -0,0 +1,18 @@ + 'Predis path inc replacement', + 'description' => 'Tests Predis path inc replacement.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'Predis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PhpRedisQueueUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PhpRedisQueueUnitTestCase.test new file mode 100644 index 00000000..a5570814 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PhpRedisQueueUnitTestCase.test @@ -0,0 +1,22 @@ + 'PhpRedis Redis queue', + 'description' => 'Ensure that Redis queue feature is working OK.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'PhpRedis'; + } +} \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PredisQueueUnitTestCase.test b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PredisQueueUnitTestCase.test new file mode 100644 index 00000000..8ad70609 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PredisQueueUnitTestCase.test @@ -0,0 +1,20 @@ + 'Predis Redis queue', + 'description' => 'Ensure that Redis queue feature is working OK.', + 'group' => 'Redis', + ); + } + + protected function getClientInterface() + { + return 'Predis'; + } +} + */ \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/QueueUnitTestCase.php b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/QueueUnitTestCase.php new file mode 100644 index 00000000..30e04dbc --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/QueueUnitTestCase.php @@ -0,0 +1,133 @@ +name = 'redis-queue-test-' . time(); + $this->queue = new Redis_Queue($this->name); + } + + public function tearDown() + { + $this->queue->deleteQueue(); + $this->name = null; + + parent::tearDown(); + } + + public function testCreate() + { + $res = $this->queue->createItem('test-queue-item-create'); + $num = $this->queue->numberOfItems(); + $this->assertEqual(1, $num); + } + + public function testClaim() + { + $data = 'test-queue-item-claimed'; + $res = $this->queue->createItem($data); + $item = $this->queue->claimItem(); + $this->assertEqual($data, $item->data); + } + + /* + public function testClaimBlocking() + { + $data = 'test-queue-item-claimed'; + $res = $this->queue->createItem($data); + $this->assertTrue($res); + $item = $this->queue->claimItemBlocking(10); + $this->assertEqual($data, $item->data); + } + */ + + public function testRelease() + { + $data = 'test-queue-item'; + + $res = $this->queue->createItem($data); + $item = $this->queue->claimItem(); + + $num = $this->queue->numberOfItems(); + $this->assertEqual(0, $num); + + $res = $this->queue->releaseItem($item); + + $num = $this->queue->numberOfItems(); + $this->assertEqual(1, $num); + } + + public function testOrder() + { + $keys = array('test1', 'test2', 'test3'); + foreach ($keys as $k) { + $this->queue->createItem($k); + } + + $first = $this->queue->claimItem(); + $this->assertEqual($first->data, $keys[0]); + + $second = $this->queue->claimItem(); + $this->assertEqual($second->data, $keys[1]); + + $this->queue->releaseItem($first); + + $third = $this->queue->claimItem(); + $this->assertEqual($third->data, $keys[2]); + + $first_again = $this->queue->claimItem(); + $this->assertEqual($first_again->data, $keys[0]); + + $num = $this->queue->numberOfItems(); + $this->assertEqual(0, $num); + } + + /* + public function lease() + { + $data = 'test-queue-item'; + $res = $this->queue->createItem($data); + $num = $this->queue->numberOfItems(); + $this->assertEquals(1, $num); + $item = $this->queue->claimItem(1); + // In Redis 2.4 the expire could be between zero to one seconds off. + sleep(2); + $expired = $this->queue->expire(); + $this->assertEquals(1, $expired); + $this->assertEquals(1, $this->queue->numberOfItems()); + // Create a second queue to test expireAll() + $q2 = new RedisQueue($this->name . '_2'); + $q2->createItem($data); + $q2->createItem($data); + $this->assertEquals(2, $q2->numberOfItems()); + $item = $this->queue->claimItem(1); + $item2 = $q2->claimItem(1); + $this->assertEquals(1, $q2->numberOfItems()); + sleep(2); + $expired = $this->queue->expireAll(); + $this->assertEquals(2, $expired); + $this->assertEquals(1, $this->queue->numberOfItems()); + $this->assertEquals(2, $q2->numberOfItems()); + $q2->deleteQueue(); + } + */ +} diff --git a/sites/all/modules/contrib/dev/redis/redis.admin.inc b/sites/all/modules/contrib/dev/redis/redis.admin.inc new file mode 100644 index 00000000..1e195e1c --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.admin.inc @@ -0,0 +1,104 @@ + 'fieldset', + '#title' => t("Connection information"), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['connection']['scheme'] = array( + '#type' => 'textfield', + '#title' => t("Scheme"), + '#default_value' => 'tcp', + '#disabled' => TRUE, + '#description' => t("Connection scheme.") . " " . t("Only tcp is currently supported. This is ignored when using a UNIX socket."), + ); + $form['connection']['redis_client_host'] = array( + '#type' => 'textfield', + '#title' => t("Host"), + '#default_value' => variable_get('redis_client_host', NULL), + '#description' => t("Redis server host. Default is @default.", array('@default' => Redis_Client_Manager::REDIS_DEFAULT_HOST)), + ); + $form['connection']['redis_client_port'] = array( + '#type' => 'textfield', + '#title' => t("Port"), + '#default_value' => variable_get('redis_client_port', NULL), + '#description' => t("Redis server port. Default is @default.", array('@default' => Redis_Client_Manager::REDIS_DEFAULT_PORT)), + ); + $form['connection']['redis_client_socket'] = array( + '#type' => 'textfield', + '#title' => t("UNIX socket"), + '#default_value' => variable_get('redis_client_socket', NULL), + '#description' => t("Redis UNIX socket for connection. If set remote server host and port will be ignored."), + ); + $form['connection']['redis_client_base'] = array( + '#type' => 'textfield', + '#title' => t("Database"), + '#default_value' => variable_get('redis_client_base', NULL), + '#description' => t("Redis server database. Default is none, Redis server will autoselect the database 0."), + ); + $form['connection']['redis_client_interface'] = array( + '#type' => 'radios', + '#title' => t("Client"), + '#options' => array( + NULL => t("None or automatic"), + 'PhpRedis' => t("PhpRedis PHP extension"), + 'Predis' => t("Predis PHP library"), + ), + '#default_value' => variable_get('redis_client_interface', NULL), + '#description' => t("Redis low level backend."), + ); + + $form = system_settings_form($form); + + // Enforce empty values drop from the $form_state in order to avoid empty + // values saving. Empty values would cause the isset() checks in client + // options to see false positives and fail upon connection. + array_unshift($form['#submit'], 'redis_settings_form_submit_clean_values'); + + return $form; +} + +/** + * Deep clean of $form_state values. + */ +function redis_settings_form_submit_clean_values($form, &$form_state) { + + $string_values = array('redis_client_host', 'redis_client_interface'); + + foreach ($string_values as $name) { + // Empty check is sufficient to verify that the field is indeed empty. + if (empty($form_state['values'][$name])) { + // Using unset() will keep the key in the array, with an associated NULL + // value. While this wouldn't really matter, it's safer to remove it so + // that system_settings_form_submit() won't find it and attempt to save + // it. + $form_state['values'] = array_diff_key($form_state['values'], array($name => NULL)); + variable_del($name); + } + } + + $numeric_values = array('redis_client_base', 'redis_client_port'); + + foreach ($numeric_values as $name) { + // Numeric values can be both of NULL or 0 (NULL meaning the value is not + // not set and the client will use the default, while 0 has a business + // meaning and should be kept as is). + if ('0' !== $form_state['values'][$name] && empty($form_state['values'][$name])) { + $form_state['values'] = array_diff_key($form_state['values'], array($name => NULL)); + variable_del($name); + } else { + $form_state['values'][$name] = (int)$form_state['values'][$name]; + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/redis.autoload.inc b/sites/all/modules/contrib/dev/redis/redis.autoload.inc new file mode 100644 index 00000000..531a6ae4 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.autoload.inc @@ -0,0 +1,25 @@ + "Redis", + 'value' => t("Connected, using the @name client.", array('@name' => Redis_Client::getClientInterfaceName())), + 'severity' => REQUIREMENT_OK, + ); + } catch (Exception $e) { + $requirements['redis'] = array( + 'title' => "Redis", + 'value' => t("Not connected."), + 'severity' => REQUIREMENT_WARNING, + 'description' => t("No Redis client connected. Please ensure that your Redis connection is working properly. If you are not using a Redis server connection you should disable this module."), + ); + } + + return $requirements; +} diff --git a/sites/all/modules/contrib/dev/redis/redis.lock.inc b/sites/all/modules/contrib/dev/redis/redis.lock.inc new file mode 100644 index 00000000..5f0391c7 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.lock.inc @@ -0,0 +1,63 @@ +getLockId(); +} + +function lock_acquire($name, $timeout = 30.0) { + return Redis_Lock::getBackend()->lockAcquire($name, $timeout); +} + +function lock_may_be_available($name) { + return Redis_Lock::getBackend()->lockMayBeAvailable($name); +} + +function lock_wait($name, $delay = 30) { + return Redis_Lock::getBackend()->lockWait($name, $delay); +} + +function lock_release($name) { + return Redis_Lock::getBackend()->lockRelease($name); +} + +function lock_release_all($lock_id = NULL) { + return Redis_Lock::getBackend()->lockReleaseAll($lock_id); +} + +// Since D6 doesn't have the drupal_register_shutdown_function +// that is called in lib/Redis/Lock/Backend/Default.php define +// the wrapper here. +if (!function_exists('drupal_register_shutdown_function')) { + function drupal_register_shutdown_function(){ + $args = func_get_args(); + call_user_func_array('register_shutdown_function', $args); + } +} diff --git a/sites/all/modules/contrib/dev/redis/redis.module b/sites/all/modules/contrib/dev/redis/redis.module new file mode 100644 index 00000000..fa4816e3 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.module @@ -0,0 +1,73 @@ + "Cache", + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items['admin/config/development/performance/redis'] = array( + 'title' => "Redis", + 'page callback' => 'drupal_get_form', + 'page arguments' => array('redis_settings_form'), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_LOCAL_TASK, + 'file' => 'redis.admin.inc', + ); + return $items; +} + +/** + * Implements hook_help(). + */ +function redis_help($path, $arg) { + switch ($path) { + case 'admin/config/development/performance/redis': + $messages = + '

' . t("Redis module is optional if you are using only a cache or lock backend. The full API will be automatically loaded and its configuration will live into the settings.php file. If you access to this screen, it's probably because another contrib module needs it as a dependency for using the Redis client. If you didn't enabled such module, you are strongly advised to disable the Redis module on the module page.") . '

' . + '

' . t("While Redis client configuration can be changed through the web, if you are using a cache or lock backend they must be set in the settings.php file. Once this done, any modification done using this form will be ignored, and real settings in use will be get at early bootstrap phase, before the configuration system is bootstrapped.") . '

'; + if (Redis_Client::getClient()) { + $messages .= '

' . t("Current connected client uses the @name library.", array('@name' => Redis_Client::getClientInterfaceName())) . '

'; + } + return $messages; + } +} + +/** + * Get Redis client for php-redis extension. + * + * @return \Redis + */ +function phpredis_client_get() { + if ('PhpRedis' !== variable_get('redis_client_interface')) { + throw new \LogicException("Redis is not configured to use the php-redis client"); + } + return Redis_Client::getClient(); +} + +/** + * Get Redis client for Predis library. + * + * @return \Predis\Client + */ +function predis_client_get() { + if ('Predis' !== variable_get('redis_client_interface')) { + throw new \LogicException("Redis is not configured to use the Predis client"); + } + return Redis_Client::getClient(); +} diff --git a/sites/all/modules/contrib/dev/redis/redis.path.inc b/sites/all/modules/contrib/dev/redis/redis.path.inc new file mode 100644 index 00000000..73ffa091 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.path.inc @@ -0,0 +1,594 @@ + variable_get('path_alias_whitelist')); + if (null === $cache['whitelist']) { + $cache['whitelist'] = drupal_path_alias_whitelist_rebuild(); + } + $denyAdmin = (bool)variable_get('path_alias_admin_blacklist', true); + } + + // If no language is explicitly specified we default to the current URL + // language. If we used a language different from the one conveyed by the + // requested URL, we might end up being unable to check if there is a path + // alias matching the URL path. + if (!$path_language = $path_language ? $path_language : $language_url->language) { + $path_language = LANGUAGE_NONE; + } + + if (!empty($path) && isset($cache[$path_language][$action][$path])) { + return $cache[$path_language][$action][$path]; + } + + if (!empty($path)) { + $path = strtolower(trim($path)); + } + + $ret = null; + $hashLookup = redis_path_backend_get(); + + switch ($action) { + + case 'wipe': + $cache = array(); + $cache['whitelist'] = drupal_path_alias_whitelist_rebuild(); + break; + + case 'alias': + if (empty($path)) { + return false; + } + // Check the path whitelist, if the top_level part before the first / + // is not in the list, then there is no need to do anything further, + // it is not in the database. + if (!isset($cache['whitelist'][strtok($path, '/')])) { + return false; + } + // Deny admin paths. + if ($denyAdmin && path_is_admin($path)) { + return false; + } + + $ret = $hashLookup->lookupAlias($path, $path_language); + if (null === $ret) { + // Original Drupal algorithm. + // This will also update the $path_language variable so Redis will store + // the right language (keeps track of LANGUAGE_NONE or specific language + // so that default fallback behavior is the same that core). + if ($path_language == LANGUAGE_NONE) { + list ($ret, $path_language) = db_query_range("SELECT alias, language FROM {url_alias} WHERE source = :source AND language = :language ORDER BY pid DESC", 0, 1, array( + ':source' => $path, + ':language' => $path_language, + ))->fetch(PDO::FETCH_NUM); + } else if ($path_language > LANGUAGE_NONE) { + list ($ret, $path_language) = db_query_range("SELECT alias, language FROM {url_alias} WHERE source = :source AND language IN (:language) ORDER BY language DESC, pid DESC", 0, 1, array( + ':source' => $path, + ':language' => array($path_language, LANGUAGE_NONE), + ))->fetch(PDO::FETCH_NUM); + } else { + list ($ret, $path_language) = db_query_range("SELECT alias, language FROM {url_alias} WHERE source = :source AND language IN (:language) ORDER BY language ASC, pid DESC", 0, 1, array( + ':source' => $path, + ':language' => array($path_language, LANGUAGE_NONE), + ))->fetch(PDO::FETCH_NUM); + } + // Getting here with a value means we need to cache it + if (empty($ret)) { + $ret = false; + } + $hashLookup->saveAlias($path, $ret, $path_language); + } + $cache[$path_language]['alias'][$path] = $ret; + $cache[$path_language]['source'][$ret] = $path; + break; + + case 'source': + if (empty($path)) { + return false; + } + // Even thought given entry is an alias, if it conflicts with an + // existing admin path just deny any lookup. + if ($denyAdmin && path_is_admin($path)) { + return false; + } + + $ret = $hashLookup->lookupSource($path, $path_language); + if (null === $ret) { + // Original Drupal algorithm. + // This will also update the $path_language variable so Redis will store + // the right language (keeps track of LANGUAGE_NONE or specific language + // so that default fallback behavior is the same that core). + if ($path_language == LANGUAGE_NONE) { + list ($ret, $path_language) = db_query_range("SELECT source, language FROM {url_alias} WHERE alias = :alias AND language = :language ORDER BY pid DESC", 0, 1, array( + ':alias' => $path, + ':language' => LANGUAGE_NONE, + ))->fetch(PDO::FETCH_NUM); + } else if ($path_language > LANGUAGE_NONE) { + list ($ret, $path_language) = db_query_range("SELECT source, language FROM {url_alias} WHERE alias = :alias AND language IN (:language) ORDER BY language DESC, pid DESC", 0, 1, array( + ':alias' => $path, + ':language' => array($path_language, LANGUAGE_NONE), + ))->fetch(PDO::FETCH_NUM); + } else { + list ($ret, $path_language) = db_query_range("SELECT source, language FROM {url_alias} WHERE alias = :alias AND language IN (:language) ORDER BY language ASC, pid DESC", 0, 1, array( + ':alias' => $path, + ':language' => array($path_language, LANGUAGE_NONE), + ))->fetch(PDO::FETCH_NUM); + } + // Getting here with a value means we need to cache it + if (empty($ret)) { + $ret = false; + } else { + $ret = strtolower(trim($ret)); + } + $hashLookup->saveAlias($ret, $path, $path_language); + } + $cache[$path_language]['alias'][$ret] = $path; + $cache[$path_language]['source'][$path] = $ret; + break; + } + + return $ret; +} + +/** + * Cache system paths for a page. + * + * Cache an array of the system paths available on each page. We assume + * that aliases will be needed for the majority of these paths during + * subsequent requests, and load them in a single query during + * drupal_lookup_path(). + */ +function drupal_cache_system_paths() { + // Check if the system paths for this page were loaded from cache in this + // request to avoid writing to cache on every request. + $cache = &drupal_static('drupal_lookup_path', array()); + if (empty($cache['system_paths']) && !empty($cache['map'])) { + // Generate a cache ID (cid) specifically for this page. + $cid = current_path(); + // The static $map array used by drupal_lookup_path() includes all + // system paths for the page request. + if ($paths = current($cache['map'])) { + $data = array_keys($paths); + $expire = REQUEST_TIME + (60 * 60 * 24); + cache_set($cid, $data, 'cache_path', $expire); + } + } +} + +/** + * Given an internal Drupal path, return the alias set by the administrator. + * + * If no path is provided, the function will return the alias of the current + * page. + * + * @param $path + * An internal Drupal path. + * @param $path_language + * An optional language code to look up the path in. + * + * @return + * An aliased path if one was found, or the original path if no alias was + * found. + */ +function drupal_get_path_alias($path = NULL, $path_language = NULL) { + // If no path is specified, use the current page's path. + if ($path == NULL) { + $path = $_GET['q']; + } + $result = $path; + if ($alias = drupal_lookup_path('alias', $path, $path_language)) { + $result = $alias; + } + return $result; +} + +/** + * Given a path alias, return the internal path it represents. + * + * @param $path + * A Drupal path alias. + * @param $path_language + * An optional language code to look up the path in. + * + * @return + * The internal path represented by the alias, or the original alias if no + * internal path was found. + */ +function drupal_get_normal_path($path, $path_language = NULL) { + $original_path = $path; + + // Lookup the path alias first. + if ($source = drupal_lookup_path('source', $path, $path_language)) { + $path = $source; + } + + // Allow other modules to alter the inbound URL. We cannot use drupal_alter() + // here because we need to run hook_url_inbound_alter() in the reverse order + // of hook_url_outbound_alter(). + foreach (array_reverse(module_implements('url_inbound_alter')) as $module) { + $function = $module . '_url_inbound_alter'; + $function($path, $original_path, $path_language); + } + + return $path; +} + +/** + * Check if the current page is the front page. + * + * @return + * Boolean value: TRUE if the current page is the front page; FALSE if otherwise. + */ +function drupal_is_front_page() { + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['is_front_page'] = &drupal_static(__FUNCTION__); + } + $is_front_page = &$drupal_static_fast['is_front_page']; + + if (!isset($is_front_page)) { + // As drupal_path_initialize updates $_GET['q'] with the 'site_frontpage' path, + // we can check it against the 'site_frontpage' variable. + $is_front_page = ($_GET['q'] == variable_get('site_frontpage', 'node')); + } + + return $is_front_page; +} + +/** + * Check if a path matches any pattern in a set of patterns. + * + * @param $path + * The path to match. + * @param $patterns + * String containing a set of patterns separated by \n, \r or \r\n. + * + * @return + * Boolean value: TRUE if the path matches a pattern, FALSE otherwise. + */ +function drupal_match_path($path, $patterns) { + $regexps = &drupal_static(__FUNCTION__); + + if (!isset($regexps[$patterns])) { + // Convert path settings to a regular expression. + // Therefore replace newlines with a logical or, /* with asterisks and the with the frontpage. + $to_replace = array( + '/(\r\n?|\n)/', // newlines + '/\\\\\*/', // asterisks + '/(^|\|)\\\\($|\|)/' // + ); + $replacements = array( + '|', + '.*', + '\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\2' + ); + $patterns_quoted = preg_quote($patterns, '/'); + $regexps[$patterns] = '/^(' . preg_replace($to_replace, $replacements, $patterns_quoted) . ')$/'; + } + return (bool)preg_match($regexps[$patterns], $path); +} + +/** + * Return the current URL path of the page being viewed. + * + * Examples: + * - http://example.com/node/306 returns "node/306". + * - http://example.com/drupalfolder/node/306 returns "node/306" while + * base_path() returns "/drupalfolder/". + * - http://example.com/path/alias (which is a path alias for node/306) returns + * "node/306" as opposed to the path alias. + * + * This function is not available in hook_boot() so use $_GET['q'] instead. + * However, be careful when doing that because in the case of Example #3 + * $_GET['q'] will contain "path/alias". If "node/306" is needed, calling + * drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available. + * + * @return + * The current Drupal URL path. + * + * @see request_path() + */ +function current_path() { + return $_GET['q']; +} + +/** + * Rebuild the path alias white list. + * + * @param $source + * An optional system path for which an alias is being inserted. + * + * @return + * An array containing a white list of path aliases. + */ +function drupal_path_alias_whitelist_rebuild($source = NULL) { + // When paths are inserted, only rebuild the whitelist if the system path + // has a top level component which is not already in the whitelist. + if (!empty($source)) { + $whitelist = variable_get('path_alias_whitelist', NULL); + if (isset($whitelist[strtok($source, '/')])) { + return $whitelist; + } + } + // For each alias in the database, get the top level component of the system + // path it corresponds to. This is the portion of the path before the first + // '/', if present, otherwise the whole path itself. + $whitelist = array(); + $result = db_query("SELECT DISTINCT SUBSTRING_INDEX(source, '/', 1) AS path FROM {url_alias}"); + foreach ($result as $row) { + $whitelist[$row->path] = TRUE; + } + variable_set('path_alias_whitelist', $whitelist); + return $whitelist; +} + +/** + * Fetches a specific URL alias from the database. + * + * @param $conditions + * A string representing the source, a number representing the pid, or an + * array of query conditions. + * + * @return + * FALSE if no alias was found or an associative array containing the + * following keys: + * - source: The internal system path. + * - alias: The URL alias. + * - pid: Unique path alias identifier. + * - language: The language of the alias. + */ +function path_load($conditions) { + if (is_numeric($conditions)) { + $conditions = array('pid' => $conditions); + } + elseif (is_string($conditions)) { + $conditions = array('source' => $conditions); + } + elseif (!is_array($conditions)) { + return FALSE; + } + $select = db_select('url_alias'); + foreach ($conditions as $field => $value) { + $select->condition($field, $value); + } + return $select + ->fields('url_alias') + ->execute() + ->fetchAssoc(); +} + +/** + * Save a path alias to the database. + * + * @param $path + * An associative array containing the following keys: + * - source: The internal system path. + * - alias: The URL alias. + * - pid: (optional) Unique path alias identifier. + * - language: (optional) The language of the alias. + */ +function path_save(&$path) { + $path += array('language' => LANGUAGE_NONE); + + // Load the stored alias, if any. + if (!empty($path['pid']) && !isset($path['original'])) { + $path['original'] = path_load($path['pid']); + } + + if (empty($path['pid'])) { + drupal_write_record('url_alias', $path); + module_invoke_all('path_insert', $path); + } + else { + drupal_write_record('url_alias', $path, array('pid')); + module_invoke_all('path_update', $path); + } + if (!empty($path['original'])) { + redis_path_backend_get()->deleteAlias($path['original']['source'], $path['original']['alias'], $path['original']['language']); + } + redis_path_backend_get()->saveAlias($path['source'], $path['alias'], $path['language']); + + // Clear internal properties. + unset($path['original']); + + // Clear the static alias cache. + drupal_clear_path_cache($path['source']); +} + +/** + * Delete a URL alias. + * + * @param $criteria + * A number representing the pid or an array of criteria. + */ +function path_delete($criteria) { + if (!is_array($criteria)) { + $criteria = array('pid' => $criteria); + } + $path = path_load($criteria); + $query = db_delete('url_alias'); + foreach ($criteria as $field => $value) { + $query->condition($field, $value); + } + $query->execute(); + module_invoke_all('path_delete', $path); + redis_path_backend_get()->deleteAlias($path['source'], $path['alias'], $path['language']); + drupal_clear_path_cache($path['source']); +} + +/** + * Determines whether a path is in the administrative section of the site. + * + * By default, paths are considered to be non-administrative. If a path does + * not match any of the patterns in path_get_admin_paths(), or if it matches + * both administrative and non-administrative patterns, it is considered + * non-administrative. + * + * @param $path + * A Drupal path. + * + * @return + * TRUE if the path is administrative, FALSE otherwise. + * + * @see path_get_admin_paths() + * @see hook_admin_paths() + * @see hook_admin_paths_alter() + */ +function path_is_admin($path) { + $path_map = &drupal_static(__FUNCTION__); + if (!isset($path_map['admin'][$path])) { + $patterns = path_get_admin_paths(); + $path_map['admin'][$path] = drupal_match_path($path, $patterns['admin']); + $path_map['non_admin'][$path] = drupal_match_path($path, $patterns['non_admin']); + } + return $path_map['admin'][$path] && !$path_map['non_admin'][$path]; +} + +/** + * Gets a list of administrative and non-administrative paths. + * + * @return array + * An associative array containing the following keys: + * 'admin': An array of administrative paths and regular expressions + * in a format suitable for drupal_match_path(). + * 'non_admin': An array of non-administrative paths and regular expressions. + * + * @see hook_admin_paths() + * @see hook_admin_paths_alter() + */ +function path_get_admin_paths() { + $patterns = &drupal_static(__FUNCTION__); + if (!isset($patterns)) { + $paths = module_invoke_all('admin_paths'); + drupal_alter('admin_paths', $paths); + // Combine all admin paths into one array, and likewise for non-admin paths, + // for easier handling. + $patterns = array(); + $patterns['admin'] = array(); + $patterns['non_admin'] = array(); + foreach ($paths as $path => $enabled) { + if ($enabled) { + $patterns['admin'][] = $path; + } + else { + $patterns['non_admin'][] = $path; + } + } + $patterns['admin'] = implode("\n", $patterns['admin']); + $patterns['non_admin'] = implode("\n", $patterns['non_admin']); + } + return $patterns; +} + +/** + * Checks a path exists and the current user has access to it. + * + * @param $path + * The path to check. + * @param $dynamic_allowed + * Whether paths with menu wildcards (like user/%) should be allowed. + * + * @return + * TRUE if it is a valid path AND the current user has access permission, + * FALSE otherwise. + */ +function drupal_valid_path($path, $dynamic_allowed = FALSE) { + global $menu_admin; + // We indicate that a menu administrator is running the menu access check. + $menu_admin = TRUE; + if ($path == '' || url_is_external($path)) { + $item = array('access' => TRUE); + } + elseif ($dynamic_allowed && preg_match('/\/\%/', $path)) { + // Path is dynamic (ie 'user/%'), so check directly against menu_router table. + if ($item = db_query("SELECT * FROM {menu_router} where path = :path", array(':path' => $path))->fetchAssoc()) { + $item['link_path'] = $item['path']; + $item['link_title'] = $item['title']; + $item['external'] = FALSE; + $item['options'] = ''; + _menu_link_translate($item); + } + } + else { + $item = menu_get_item($path); + } + $menu_admin = FALSE; + return $item && $item['access']; +} + +/** + * Clear the path cache. + * + * @param $source + * An optional system path for which an alias is being changed. + */ +function drupal_clear_path_cache($source = NULL) { + // Clear the drupal_lookup_path() static cache. + drupal_static_reset('drupal_lookup_path'); + drupal_path_alias_whitelist_rebuild($source); +}