Browse Source

added redis contrib module

Bachir Soussi Chiadmi 6 years ago
parent
commit
18f4aba146
69 changed files with 6525 additions and 0 deletions
  1. 1 0
      sites/all/modules/contrib/dev/redis/.gitignore
  2. 339 0
      sites/all/modules/contrib/dev/redis/LICENSE.txt
  3. 37 0
      sites/all/modules/contrib/dev/redis/README.PhpRedis.txt
  4. 60 0
      sites/all/modules/contrib/dev/redis/README.Predis.txt
  5. 40 0
      sites/all/modules/contrib/dev/redis/README.testing.txt
  6. 474 0
      sites/all/modules/contrib/dev/redis/README.txt
  7. 108 0
      sites/all/modules/contrib/dev/redis/lib/Redis/AbstractBackend.php
  8. 59 0
      sites/all/modules/contrib/dev/redis/lib/Redis/BackendInterface.php
  9. 655 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Cache.php
  10. 102 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Cache/BackendInterface.php
  11. 39 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Base.php
  12. 149 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Cache/PhpRedis.php
  13. 145 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php
  14. 66 0
      sites/all/modules/contrib/dev/redis/lib/Redis/CacheCompressed.php
  15. 241 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Client.php
  16. 32 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Client/FactoryInterface.php
  17. 144 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Client/Manager.php
  18. 36 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Client/PhpRedis.php
  19. 145 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php
  20. 31 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php
  21. 61 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Lock/BackendInterface.php
  22. 89 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Lock/DefaultBackend.php
  23. 138 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php
  24. 137 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php
  25. 105 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Path/AbstractHashLookup.php
  26. 109 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Path/HashLookupInterface.php
  27. 27 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Path/NullHashLookup.php
  28. 108 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Path/PhpRedis.php
  29. 108 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php
  30. 58 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php
  31. 99 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php
  32. 106 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php
  33. 141 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/AbstractUnitTestCase.php
  34. 60 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Admin/VariableTestCase.test
  35. 27 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFixesUnitTestCase.test
  36. 27 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFlushUnitTestCase.test
  37. 25 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFixesUnitTestCase.test
  38. 25 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFlushUnitTestCase.test
  39. 25 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedWithPipelineFixesUnitTestCase.test
  40. 209 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FixesUnitTestCase.php
  41. 185 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FlushUnitTestCase.php
  42. 22 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFixesUnitTestCase.test
  43. 22 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFlushUnitTestCase.test
  44. 20 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFixesUnitTestCase.test
  45. 20 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFlushUnitTestCase.test
  46. 20 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedWithPipelineFixesUnitTestCase.test
  47. 18 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFixesUnitTestCase.test
  48. 18 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFlushUnitTestCase.test
  49. 20 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFixesUnitTestCase.test
  50. 20 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFlushUnitTestCase.test
  51. 20 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedWithPipelineFixesUnitTestCase.test
  52. 66 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/ClientUnitTestCase.test
  53. 14 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/MockFactory.php
  54. 119 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/LockingUnitTestCase.php
  55. 27 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PhpRedisLockingUnitTestCase.test
  56. 23 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PredisLockingUnitTestCase.test
  57. 148 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PathUnitTestCase.php
  58. 22 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PhpRedisPathUnitTestCase.test
  59. 18 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PredisPathUnitTestCase.test
  60. 22 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PhpRedisQueueUnitTestCase.test
  61. 20 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PredisQueueUnitTestCase.test
  62. 133 0
      sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/QueueUnitTestCase.php
  63. 104 0
      sites/all/modules/contrib/dev/redis/redis.admin.inc
  64. 25 0
      sites/all/modules/contrib/dev/redis/redis.autoload.inc
  65. 44 0
      sites/all/modules/contrib/dev/redis/redis.info
  66. 38 0
      sites/all/modules/contrib/dev/redis/redis.install
  67. 63 0
      sites/all/modules/contrib/dev/redis/redis.lock.inc
  68. 73 0
      sites/all/modules/contrib/dev/redis/redis.module
  69. 594 0
      sites/all/modules/contrib/dev/redis/redis.path.inc

+ 1 - 0
sites/all/modules/contrib/dev/redis/.gitignore

@@ -0,0 +1 @@
+predis

+ 339 - 0
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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 37 - 0
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.

+ 60 - 0
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.

+ 40 - 0
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"

+ 474 - 0
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.

+ 108 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/AbstractBackend.php

@@ -0,0 +1,108 @@
+<?php
+
+abstract class Redis_AbstractBackend implements Redis_BackendInterface
+{
+    /**
+     * Key components name separator
+     */
+    const KEY_SEPARATOR = ':';
+
+    /**
+     * @var string
+     */
+    private $prefix;
+
+    /**
+     * @var string
+     */
+    private $namespace;
+
+    /**
+     * @var mixed
+     */
+    private $client;
+
+    /**
+     * Default constructor
+     *
+     * @param mixed $client
+     *   Redis client
+     * @param string $namespace
+     *   Component namespace
+     * @param string $prefix
+     *   Component prefix
+     */
+    public function __construct($client, $namespace = null, $prefix = null)
+    {
+        $this->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));
+    }
+}

+ 59 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/BackendInterface.php

@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Client based Redis component
+ */
+interface Redis_BackendInterface
+{
+    /**
+     * Set client
+     *
+     * @param mixed $client
+     */
+    public function setClient($client);
+
+    /**
+     * Get client
+     *
+     * @return mixed
+     */
+    public function getClient();
+
+    /**
+     * Set prefix
+     *
+     * @param string $prefix
+     */
+    public function setPrefix($prefix);
+
+    /**
+     * Get prefix
+     *
+     * @return string
+     */
+    public function getPrefix();
+
+    /**
+     * Set namespace
+     *
+     * @param string $namespace
+     */
+    public function setNamespace($namespace);
+
+    /**
+     * Get namespace
+     *
+     * @return string
+     */
+    public function getNamespace();
+
+    /**
+     * Get full key name using the set prefix
+     *
+     * @param string ...
+     *   Any numer of strings to append to path using the separator
+     *
+     * @return string
+     */
+    public function getKey();
+}

+ 655 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Cache.php

@@ -0,0 +1,655 @@
+<?php
+
+/**
+ * Because those objects will be spawned during boostrap all its configuration
+ * must be set in the settings.php file.
+ *
+ * You will find the driver specific implementation in the Redis_Cache_*
+ * classes as they may differ in how the API handles transaction, pipelining
+ * and return values.
+ */
+class Redis_Cache
+    implements DrupalCacheInterface
+{
+    /**
+     * Default lifetime for permanent items.
+     * Approximatively 1 year.
+     */
+    const LIFETIME_PERM_DEFAULT = 31536000;
+
+    /**
+     * Uses EVAL scripts to flush data when called
+     *
+     * This remains the default behavior and is safe until you use a single
+     * Redis server instance and its version is >= 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();
+        }
+    }
+}

+ 102 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/BackendInterface.php

@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * Real cache backend primitives. This functions will be used by the
+ * Redis_Cache wrapper class that implements the high-level logic that
+ * allows us to be Drupal compatible.
+ */
+interface Redis_Cache_BackendInterface extends Redis_BackendInterface
+{
+    /**
+     * Defaut constructor
+     *
+     * @param string $namespace
+     */
+    public function __construct($client, $namespace);
+
+    /**
+     * Get namespace
+     *
+     * @return string
+     */
+    public function getNamespace();
+
+    /**
+     * Set last flush time
+     *
+     * @param int $time
+     * @param boolean $volatile
+     */
+    public function setLastFlushTimeFor($time, $volatile = false);
+
+    /**
+     * Get last flush time
+     *
+     * @return int[]
+     *   First value is for non-volatile items, second value is for volatile items.
+     */
+    public function getLastFlushTime();
+
+    /**
+     * Get a single entry
+     *
+     * @param string $id
+     *
+     * @return stdClass
+     *   Cache entry or false if the entry does not exists.
+     */
+    public function get($id);
+
+    /**
+     * Get multiple entries
+     *
+     * @param string[] $idList
+     *
+     * @return stdClass[]
+     *   Existing cache entries keyed by id,
+     */
+    public function getMultiple(array $idList);
+
+    /**
+     * Set a single entry
+     *
+     * @param string $id
+     * @param mixed $data
+     * @param int $ttl
+     * @param boolean $volatile
+     */
+    public function set($id, $data, $ttl = null, $volatile = false);
+
+    /**
+     * Delete a single entry
+     *
+     * @param string $cid
+     */
+    public function delete($id);
+
+    /**
+     * Delete multiple entries
+     *
+     * This method should not use a single DEL command but use a pipeline instead
+     *
+     * @param array $idList
+     */
+    public function deleteMultiple(array $idList);
+
+    /**
+     * Delete entries by prefix
+     *
+     * @param string $prefix
+     */
+    public function deleteByPrefix($prefix);
+
+    /**
+     * Flush all entries
+     */
+    public function flush();
+
+    /**
+     * Flush all entries marked as temporary
+     */
+    public function flushVolatile();
+}

+ 39 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Base.php

@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @todo
+ *   - Improve lua scripts by using SCAN family commands
+ *   - Deambiguate why we need the namespace only for flush*() operations
+ *   - Implement the isEmpty() method by using SCAN or KEYS
+ */
+abstract class Redis_Cache_Base extends Redis_AbstractBackend
+{
+    /**
+     * Lastest cache flush KEY name
+     */
+    const LAST_FLUSH_KEY = '_last_flush';
+
+    /**
+     * Delete by prefix lua script
+     */
+    const EVAL_DELETE_PREFIX = <<<EOT
+local keys = redis.call("KEYS", ARGV[1])
+for i, k in ipairs(keys) do
+    redis.call("DEL", k)
+end
+return 1
+EOT;
+
+    /**
+     * Delete volatile by prefix lua script
+     */
+    const EVAL_DELETE_VOLATILE = <<<EOT
+local keys = redis.call('KEYS', ARGV[1])
+for i, k in ipairs(keys) do
+    if "1" == redis.call("HGET", k, "volatile") then
+        redis.call("DEL", k)
+    end
+end
+return 1
+EOT;
+}

+ 149 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/PhpRedis.php

@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * Predis cache backend.
+ */
+class Redis_Cache_PhpRedis extends Redis_Cache_Base
+{
+    public function setLastFlushTimeFor($time, $volatile = false)
+    {
+        $client = $this->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);
+        }
+    }
+}

+ 145 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php

@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * Predis cache backend.
+ */
+class Redis_Cache_Predis extends Redis_Cache_Base
+{
+    public function setLastFlushTimeFor($time, $volatile = false)
+    {
+        $client = $this->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);
+        }
+    }
+}

+ 66 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/CacheCompressed.php

@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * This typically brings 80..85% compression in ~20ms/mb write, 5ms/mb read.
+ */
+class Redis_CacheCompressed extends Redis_Cache implements DrupalCacheInterface
+{
+    private $compressionSizeThreshold = 100;
+    private $compressionRatio = 1;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function __construct($bin)
+    {
+        parent::__construct($bin);
+
+        $this->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);
+    }
+}

+ 241 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Client.php

@@ -0,0 +1,241 @@
+<?php
+
+// It may happen we get here with no autoloader set during the Drupal core
+// early bootstrap phase, at cache backend init time.
+if (!interface_exists('Redis_Client_FactoryInterface')) {
+    require_once dirname(__FILE__) . '/Client/FactoryInterface.php';
+    require_once dirname(__FILE__) . '/Client/Manager.php';
+}
+
+/**
+ * This static class only reason to exist is to tie Drupal global
+ * configuration to OOP driven code of this module: it will handle
+ * everything that must be read from global configuration and let
+ * other components live without any existence of it
+ */
+class Redis_Client
+{
+    /**
+     * Cache implementation namespace.
+     */
+    const REDIS_IMPL_CACHE = 'Redis_Cache_';
+
+    /**
+     * Lock implementation namespace.
+     */
+    const REDIS_IMPL_LOCK = 'Redis_Lock_';
+
+    /**
+     * Cache implementation namespace.
+     */
+    const REDIS_IMPL_QUEUE = 'Redis_Queue_';
+
+    /**
+     * Path implementation namespace.
+     */
+    const REDIS_IMPL_PATH = 'Redis_Path_';
+
+    /**
+     * Client factory implementation namespace.
+     */
+    const REDIS_IMPL_CLIENT = 'Redis_Client_';
+
+    /**
+     * @var Redis_Client_Manager
+     */
+    private static $manager;
+
+    /**
+     * @var string
+     */
+    static protected $globalPrefix;
+
+    /**
+     * Get site default global prefix
+     *
+     * @return string
+     */
+    static public function getGlobalPrefix()
+    {
+        // Provide a fallback for multisite. This is on purpose not inside the
+        // getPrefixForBin() function in order to decouple the unified prefix
+        // variable logic and custom module related security logic, that is not
+        // necessary for all backends. We can't just use HTTP_HOST, as multiple
+        // hosts might be using the same database. Or, more commonly, a site
+        // might not be a multisite at all, but might be using Drush leading to
+        // a separate HTTP_HOST of 'default'. Likewise, we can't rely on
+        // conf_path(), as settings.php might be modifying what database to
+        // connect to. To mirror what core does with database caching we use
+        // the DB credentials to inform our cache key.
+      if (null === self::$globalPrefix) {
+            if (isset($GLOBALS['db_url']) && is_string($GLOBALS['db_url'])) {
+                // Drupal 6 specifics when using the cache_backport module, we
+                // therefore cannot use \Database class to determine database
+                // settings.
+              self::$globalPrefix = md5($GLOBALS['db_url']);
+            } else {
+                require_once DRUPAL_ROOT . '/includes/database/database.inc';
+                $dbInfo = Database::getConnectionInfo();
+                $active = $dbInfo['default'];
+                self::$globalPrefix = md5($active['host'] . $active['database'] . $active['prefix']['default']);
+            }
+        }
+
+        return self::$globalPrefix;
+    }
+
+    /**
+     * Get global default prefix
+     *
+     * @param string $namespace
+     *
+     * @return string
+     */
+    static public function getDefaultPrefix($namespace = null)
+    {
+        $ret = null;
+
+        if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
+            $ret = $GLOBALS['drupal_test_info']['test_run_id'];
+        } else {
+            $prefixes = variable_get('cache_prefix', null);
+
+            if (is_string($prefixes)) {
+                // Variable can be a string which then considered as a default
+                // behavior.
+                $ret = $prefixes;
+            } else if (null !== $namespace && isset($prefixes[$namespace])) {
+                if (false !== $prefixes[$namespace]) {
+                    // If entry is set and not false an explicit prefix is set
+                    // for the bin.
+                    $ret = $prefixes[$namespace];
+                } else {
+                    // If we have an explicit false it means no prefix whatever
+                    // is the default configuration.
+                    $ret = '';
+                }
+            } else {
+                // Key is not set, we can safely rely on default behavior.
+                if (isset($prefixes['default']) && false !== $prefixes['default']) {
+                    $ret = $prefixes['default'];
+                } else {
+                    // When default is not set or an explicit false this means
+                    // no prefix.
+                    $ret = '';
+                }
+            }
+        }
+
+        if (empty($ret)) {
+            $ret = Redis_Client::getGlobalPrefix();
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Get client manager
+     *
+     * @return Redis_Client_Manager
+     */
+    static public function getManager()
+    {
+        global $conf;
+
+        if (null === self::$manager) {
+
+            $className = self::getClass(self::REDIS_IMPL_CLIENT);
+            $factory = new $className();
+
+            // Build server list from conf
+            $serverList = array();
+            if (isset($conf['redis_servers'])) {
+                $serverList = $conf['redis_servers'];
+            }
+
+            if (empty($serverList) || !isset($serverList['default'])) {
+
+                // Backward configuration compatibility with older versions
+                $serverList[Redis_Client_Manager::REALM_DEFAULT] = array();
+
+                foreach (array('host', 'port', 'base', 'password', 'socket') as $key) {
+                    if (isset($conf['redis_client_' . $key])) {
+                        $serverList[Redis_Client_Manager::REALM_DEFAULT][$key] = $conf['redis_client_' . $key];
+                    }
+                }
+            }
+
+            self::$manager = new Redis_Client_Manager($factory, $serverList);
+        }
+
+        return self::$manager;
+    }
+
+    /**
+     * Find client class name
+     *
+     * @return string
+     */
+    static public function getClientInterfaceName()
+    {
+        global $conf;
+
+        if (!empty($conf['redis_client_interface'])) {
+            return $conf['redis_client_interface'];
+        } else if (class_exists('Predis\Client')) {
+            // Transparent and abitrary preference for Predis library.
+            return  $conf['redis_client_interface'] = 'Predis';
+        } else if (class_exists('Redis')) {
+            // Fallback on PhpRedis if available.
+            return $conf['redis_client_interface'] = 'PhpRedis';
+        } else {
+            throw new Exception("No client interface set.");
+        }
+    }
+
+    /**
+     * For unit test use only
+     */
+    static public function reset(Redis_Client_Manager $manager = null)
+    {
+        self::$manager = $manager;
+    }
+
+    /**
+     * Get the client for the 'default' realm
+     *
+     * @return mixed
+     *
+     * @deprecated
+     */
+    public static function getClient()
+    {
+        return self::getManager()->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;
+    }
+}
+

+ 32 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Client/FactoryInterface.php

@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Client proxy, client handling class tied to the bare mininum.
+ */
+interface Redis_Client_FactoryInterface {
+  /**
+   * Get the connected client instance.
+   *
+   * @param array $options
+   *   Options from the server pool configuration that may contain:
+   *     - host
+   *     - port
+   *     - database
+   *     - password
+   *     - socket
+   *
+   * @return mixed
+   *   Real client depends from the library behind.
+   */
+  public function getClient($options = array());
+
+  /**
+   * Get underlaying library name used.
+   * 
+   * This can be useful for contribution code that may work with only some of
+   * the provided clients.
+   * 
+   * @return string
+   */
+  public function getName();
+}

+ 144 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Manager.php

@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * Client pool manager for multi-server configurations
+ */
+class Redis_Client_Manager
+{
+    /**
+     * Redis default host
+     */
+    const REDIS_DEFAULT_HOST = '127.0.0.1';
+
+    /**
+     * Redis default port
+     */
+    const REDIS_DEFAULT_PORT = 6379;
+
+    /**
+     * Redis default socket (will override host and port)
+     */
+    const REDIS_DEFAULT_SOCKET = null;
+
+    /**
+     * Redis default database: will select none (Database 0)
+     */
+    const REDIS_DEFAULT_BASE = null;
+
+    /**
+     * Redis default password: will not authenticate
+     */
+    const REDIS_DEFAULT_PASSWORD = null;
+
+    /**
+     * Default realm
+     */
+    const REALM_DEFAULT = 'default';
+
+    /**
+     * Client interface name (PhpRedis or Predis)
+     *
+     * @var string
+     */
+    private $interfaceName;
+
+    /**
+     * @var array[]
+     */
+    private $serverList = array();
+
+    /**
+     * @var mixed[]
+     */
+    private $clients = array();
+
+    /**
+     * @var Redis_Client_FactoryInterface
+     */
+    private $factory;
+
+    /**
+     * Default constructor
+     *
+     * @param Redis_Client_FactoryInterface $factory
+     *   Client factory
+     * @param array $serverList
+     *   Server connection info list
+     */
+    public function __construct(Redis_Client_FactoryInterface $factory, $serverList = array())
+    {
+        $this->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);
+    }
+}

+ 36 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Client/PhpRedis.php

@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * PhpRedis client specific implementation.
+ */
+class Redis_Client_PhpRedis implements Redis_Client_FactoryInterface {
+
+  public function getClient($options = array()) {
+    $client = new Redis;
+
+    if (!empty($options['socket'])) {
+      $client->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';
+  }
+}

+ 145 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php

@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * Predis client specific implementation.
+ */
+class Redis_Client_Predis implements Redis_Client_FactoryInterface {
+
+  /**
+   * Circular depedency breaker.
+   */
+  static protected $autoloaderRegistered = false;
+
+  /**
+   * If the first cache get operation happens after the core autoloader has
+   * been registered to PHP, during our autoloader registration we will
+   * trigger it when calling class_exists(): core autoloader will then run
+   * cache_get() during autoloading but sadly this will run our autoloader
+   * registration once again. The second time we are called the circular
+   * dependency breaker will act and we will do nothing, ending up in a
+   * class instanciation attempt while the autoloader is still not loaded.
+   */
+  static protected $stupidCoreWorkaround = 0;
+
+  /**
+   * Define Predis base path if not already set, and if we need to set the
+   * autoloader by ourself. This will ensure no crash. Best way would have
+   * been that Drupal ships a PSR-0 autoloader, in which we could manually
+   * add our library path.
+   * 
+   * We cannot do that in the file header, PHP class_exists() function wont
+   * see classes being loaded during the autoloading because this file is
+   * loaded by another autoloader: attempting the class_exists() during a
+   * pending autoloading would cause PHP to crash and ignore the rest of the
+   * file silentely (WTF!?). By delaying this at the getClient() call we
+   * ensure we are not in the class loading process anymore.
+   */
+  public static function setPredisAutoload() {
+
+    if (self::$autoloaderRegistered) {
+      return;
+    }
+
+    self::$stupidCoreWorkaround++;
+
+    // If you attempt to set Drupal's bin cache_bootstrap using Redis, you
+    // will experience an infinite loop (breaking by itself the second time
+    // it passes by): the following call will wake up autoloaders (and we
+    // want that to work since user may have set its own autoloader) but
+    // will wake up Drupal's one too, and because Drupal core caches its
+    // file map, this will trigger this method to be called a second time
+    // and boom! Adios bye bye. That's why this will be called early in the
+    // 'redis.autoload.inc' file instead.
+    if (1 < self::$stupidCoreWorkaround || !class_exists('Predis\Client')) {
+
+      if (!defined('PREDIS_BASE_PATH')) {
+        $search = DRUPAL_ROOT . '/sites/all/libraries/predis';
+        define('PREDIS_BASE_PATH', $search);
+      } else {
+        $search = PREDIS_BASE_PATH;
+      }
+
+      if (is_dir($search . '/src')) { // Predis v1.x
+        define('PREDIS_VERSION_MAJOR', 1);
+      } else if (is_dir($search . '/lib')) { // Predis v0.x
+        define('PREDIS_VERSION_MAJOR', 0);
+      } else {
+        throw new Exception("PREDIS_BASE_PATH constant must be set, Predis library must live in sites/all/libraries/predis.");
+      }
+
+      // Register a simple autoloader for Predis library. Since the Predis
+      // library is PHP 5.3 only, we can afford doing closures safely.
+      switch (PREDIS_VERSION_MAJOR) {
+
+        case 0:
+          $autoload = function($classname) { // PSR-0 autoloader.
+            if (0 === strpos($classname, 'Predis\\')) {
+              $filename = PREDIS_BASE_PATH . '/lib/' . str_replace('\\', '/', $classname) . '.php';
+              return (bool)require_once $filename;
+            }
+            return false;
+          };
+          break;
+
+        case 1:
+          // Register a simple autoloader for Predis library. Since the Predis
+          // library is PHP 5.3 only, we can afford doing closures safely.
+          $autoload = function($classname) { // PSR-4 autoloader
+            if (0 === strpos($classname, 'Predis\\')) {
+              $filename = PREDIS_BASE_PATH . '/src/' . str_replace('\\', '/', substr($classname, 7)) . '.php';
+              return (bool)require_once $filename;
+            }
+            return false;
+          };
+          break;
+      }
+
+      if ($autoload) {
+        spl_autoload_register($autoload);
+      }
+
+      // Same reason why we have the stupid core workaround, if this happens
+      // during a second autoload call, PHP won't call the newly registered
+      // autoloader function, so just load the file.
+      if (1 < self::$stupidCoreWorkaround) {
+        call_user_func($autoload, 'Predis\Client');
+      }
+    }
+
+    self::$autoloaderRegistered = true;
+  }
+
+  public function getClient($options = array()) {
+
+    self::setPredisAutoload();
+
+    if (!empty($options['socket'])) {
+      $options['scheme'] = 'unix';
+      $options['path'] = $options['socket'];
+    }
+
+    foreach ($options as $key => $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';
+  }
+}

+ 31 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php

@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Lock backend singleton handling.
+ */
+class Redis_Lock {
+    /**
+     * @var Redis_Lock_BackendInterface
+     */
+    private static $instance;
+
+    /**
+     * Get actual lock backend.
+     * 
+     * @return Redis_Lock_BackendInterface
+     */
+    public static function getBackend()
+    {
+        if (!isset(self::$instance)) {
+
+            $className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_LOCK);
+
+            self::$instance = new $className(
+                Redis_Client::getClient(),
+                Redis_Client::getDefaultPrefix('lock')
+            );
+        }
+
+        return self::$instance;
+    }
+}

+ 61 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/BackendInterface.php

@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Lock backend interface.
+ */
+interface Redis_Lock_BackendInterface {
+  /**
+   * Acquire lock.
+   * 
+   * @param string $name
+   *   Lock name.
+   * @param float $timeout = 30.0
+   *   (optional) Lock lifetime in seconds.
+   * 
+   * @return bool
+   */
+  public function lockAcquire($name, $timeout = 30.0);
+
+  /**
+   * Check if lock is available for acquire.
+   * 
+   * @param string $name
+   *   Lock to acquire.
+   * 
+   * @return bool
+   */
+  public function lockMayBeAvailable($name);
+
+  /**
+   * Wait a short amount of time before a second lock acquire attempt.
+   * 
+   * @param string $name
+   *   Lock name currently being locked.
+   * @param int $delay = 30
+   *   Miliseconds to wait for.
+   */
+  public function lockWait($name, $delay = 30);
+
+  /**
+   * Release given lock.
+   * 
+   * @param string $name
+   */
+  public function lockRelease($name);
+
+  /**
+   * Release all locks for the given lock token identifier.
+   * 
+   * @param string $lockId = NULL
+   *   (optional) If none given, remove all lock from the current page.
+   */
+  public function lockReleaseAll($lock_id = NULL);
+
+  /**
+   * Get the unique page token for locks. Locks will be wipeout at each end of
+   * page request on a token basis.
+   * 
+   * @return string
+   */
+  public function getLockId();
+}

+ 89 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/DefaultBackend.php

@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * Lock backend shared methods.
+ */
+abstract class Redis_Lock_DefaultBackend
+    extends Redis_AbstractBackend
+    implements Redis_Lock_BackendInterface
+{
+  /**
+   * Current page lock token identifier.
+   *
+   * @var string
+   */
+  protected $_lockId;
+
+  /**
+   * Existing locks for this page.
+   *
+   * @var array
+   */
+  protected $_locks = array();
+
+  /**
+   * Default implementation from actual Drupal core.
+   *
+   * @see Redis_Lock_BackendInterface::lockWait()
+   */
+  public function lockWait($name, $delay = 30) {
+    // Pause the process for short periods between calling
+    // lock_may_be_available(). This prevents hitting the database with constant
+    // database queries while waiting, which could lead to performance issues.
+    // However, if the wait period is too long, there is the potential for a
+    // large number of processes to be blocked waiting for a lock, especially
+    // if the item being rebuilt is commonly requested. To address both of these
+    // concerns, begin waiting for 25ms, then add 25ms to the wait period each
+    // time until it reaches 500ms. After this point polling will continue every
+    // 500ms until $delay is reached.
+
+    // $delay is passed in seconds, but we will be using usleep(), which takes
+    // microseconds as a parameter. Multiply it by 1 million so that all
+    // further numbers are equivalent.
+    $delay = (int) $delay * 1000000;
+
+    // Begin sleeping at 25ms.
+    $sleep = 25000;
+    while ($delay > 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));
+    }
+  }
+}

+ 138 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php

@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * Predis lock backend implementation.
+ *
+ * This implementation works with a single key per lock so is viable when
+ * doing client side sharding and/or using consistent hashing algorithm.
+ */
+class Redis_Lock_PhpRedis extends Redis_Lock_DefaultBackend {
+
+  public function lockAcquire($name, $timeout = 30.0) {
+    $client = $this->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);
+      }
+    }
+  }
+}

+ 137 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php

@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * Predis lock backend implementation.
+ *
+ * This implementation works with a single key per lock so is viable when
+ * doing client side sharding and/or using consistent hashing algorithm.
+ */
+class Redis_Lock_Predis extends Redis_Lock_DefaultBackend {
+
+  public function lockAcquire($name, $timeout = 30.0) {
+    $client = $this->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));
+      }
+    }
+  }
+}
+

+ 105 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Path/AbstractHashLookup.php

@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * Common implementation for Redis-based implementations
+ */
+abstract class Redis_Path_AbstractHashLookup extends Redis_AbstractBackend implements
+    Redis_Path_HashLookupInterface
+{
+    /**
+     * @todo document me
+     *
+     * @param string $key
+     * @param string $hkey
+     * @param string $hvalue
+     */
+    abstract protected function saveInHash($key, $hkey, $hvalue);
+
+    /**
+     * @todo document me
+     *
+     * @param string $key
+     * @param string $hkey
+     * @param string $hvalue
+     */
+    abstract protected function deleteInHash($key, $hkey, $hvalue);
+
+    /**
+     * @todo document me
+     *
+     * @param string $keyPrefix
+     * @param string $hkey
+     * @param string $language
+     */
+    abstract protected function lookupInHash($keyPrefix, $hkey, $language = null);
+
+    /**
+     * Normalize value to avoid duplicate or false negatives
+     *
+     * @param string $value
+     *
+     * @return string
+     */
+    private function normalize($value)
+    {
+        if (null !== $value) {
+            return strtolower(trim($value));
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveAlias($source, $alias, $language = null)
+    {
+        $alias  = $this->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);
+    }
+}

+ 109 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Path/HashLookupInterface.php

@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * Very fast hash based lookup interface.
+ *
+ * This will work for any key-value store whether it's APC, Redis, memcache...
+ * Rationale behind this is that Drupal calls hundreds of time per request the
+ * drupal_lookup_path() function and we need it to be very fast. The key of
+ * success to keep it stupid simple and coherent as the same time is that we
+ * consider this backend as a cache (more or less permanent) that might be
+ * cleared at any time, and synchronized as when necessary or incrementally.
+ * This should be very fast.
+ *
+ * Redis implementation will be the following:
+ *
+ * Aliases are stored into a Redis HASH and are stored per language basis.
+ * Key is:
+ *   [SITEPREFIX:]path:dst:LANGUAGE
+ * Keys inside the hash are a MD5() of the source and values are the alias
+ *
+ * Sources are also stored the same way except the HASH key is the following:
+ *   [SITEPREFIX:]path:src:LANGUAGE
+ * Keys inside the hash are a MD5() of the alias and values are the sources.
+ *
+ * In both case values are a comma separated list of string values.
+ *
+ * The MD5() should give us low collision algorithm and we'll keep it until
+ * no one experiences any problem.
+ *
+ * Alias and sources are always looked up using the language, hence the
+ * different keys for different languages.
+ */
+interface Redis_Path_HashLookupInterface
+{
+    /**
+     * Alias HASH key prefix
+     */
+    const KEY_ALIAS = 'path:a';
+
+    /**
+     * Source HASH key prefix
+     */
+    const KEY_SOURCE = 'path:s';
+
+    /**
+     * Null value (not existing yet cached value)
+     */
+    const VALUE_NULL = '!';
+
+    /**
+     * Values separator for hash values
+     */
+    const VALUE_SEPARATOR = '#';
+
+    /**
+     * Alias is being inserted with the given source
+     *
+     * @param string $source
+     * @param string $alias
+     * @param string $language
+     */
+    public function saveAlias($source, $alias, $language = null);
+
+    /**
+     * Alias is being deleted for the given source
+     *
+     * @param string $source
+     * @param string $alias
+     * @param string $language
+     */
+    public function deleteAlias($source, $alias, $language = null);
+
+    /**
+     * A language is being deleted
+     *
+     * @param string $language
+     */
+    public function deleteLanguage($language);
+
+    /**
+     * Lookup any alias for the given source
+     *
+     * First that has been inserted wins over the others
+     *
+     * @param string $source
+     * @param string $language
+     *
+     * @return string|null|false
+     *   - The string value if found
+     *   - null if not found
+     *   - false if set as non existing
+     */
+    public function lookupAlias($source, $language = null);
+
+    /**
+     * Lookup any source for the given alias
+     *
+     * First that has been inserted wins over the others
+     *
+     * @param string $alias
+     * @param string $language
+     *
+     * @return string|null|false
+     *   - The string value if found
+     *   - null if not found
+     *   - false if set as non existing
+     */
+    public function lookupSource($alias, $language = null);
+}

+ 27 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Path/NullHashLookup.php

@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Null implementation.
+ */
+class Redis_Path_NullHashLookup implements Redis_Path_HashLookupInterface
+{
+    public function saveAlias($source, $alias, $language = null)
+    {
+    }
+
+    public function deleteAlias($source, $alias, $language = null)
+    {
+    }
+
+    public function deleteLanguage($language)
+    {
+    }
+
+    public function lookupAlias($source, $language = null)
+    {
+    }
+
+    public function lookupSource($alias, $language = null)
+    {
+    }
+}

+ 108 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Path/PhpRedis.php

@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * PhpRedis implementation.
+ *
+ * @todo
+ *   Set high expire value to the hash for rotation when memory is empty
+ *   React upon cache clear all and rebuild path list?
+ */
+class Redis_Path_PhpRedis extends Redis_Path_AbstractHashLookup
+{
+    protected function saveInHash($key, $hkey, $hvalue)
+    {
+        $client = $this->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)));
+    }
+}

+ 108 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php

@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * PhpRedis implementation.
+ *
+ * @todo
+ *   Set high expire value to the hash for rotation when memory is empty
+ *   React upon cache clear all and rebuild path list?
+ */
+class Redis_Path_Predis extends Redis_Path_AbstractHashLookup
+{
+    protected function saveInHash($key, $hkey, $hvalue)
+    {
+        $client = $this->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)));
+    }
+}

+ 58 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php

@@ -0,0 +1,58 @@
+<?php
+
+class Redis_Queue implements DrupalReliableQueueInterface
+{
+    /**
+     * @var DrupalQueueInterface
+     */
+    protected $backend;
+
+    /**
+     * Default contructor
+     *
+     * Beware that DrupalQueueInterface does not defines the __construct
+     * method in the interface yet is being used from DrupalQueue::get() 
+     *
+     * @param unknown $name
+     */
+    public function __construct($name)
+    {
+        $className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_QUEUE);
+        $this->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();
+    }
+}

+ 99 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php

@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * Redis allows implementing reliable queues, here is the spec:
+ *
+ *  - For each queue, you have 4 different HASH:
+ *
+ *     - One for queued items queue:NAME:queued
+ *
+ *     - One for claimed items being processed: queue:NAME:claimed
+ *
+ *     - One for claimed items leave time: queue:NAME:leave
+ *       Items from this one will be arbitrarily fetched at cron
+ *       time and released when leave is outdated.
+ *
+ *     - One containing the item values and other valuable stateful
+ *       information: queue:NAME:data ;
+ *
+ *        - For example, current job maximum identifier (auto increment
+ *          emulation) will be stored in the "sequence" HASH key
+ *
+ *        - All other keys within the HASH will be the items themselves,
+ *          keys for those will always be numeric
+ *
+ *     - Each time a queue will be emptied, even during a pragmatic process,
+ *       it will be automatically deleted, reseting the sequence counter to
+ *       the 0 value each time
+ *
+ *  - Algorithm is a variation of the one described in "Reliable queue"
+ *    section of http://redis.io/commands/rpoplpush and partial port of what
+ *    you can find in the http://drupal.org/project/redis_queue module.
+ *
+ * You will find the driver specific implementation in the Redis_Queue_*
+ * classes as they may differ in how the API handles transaction, pipelining
+ * and return values.
+ */
+abstract class Redis_Queue_Base extends Redis_AbstractBackend implements
+    DrupalReliableQueueInterface
+{
+    /**
+     * Key prefix for queue data.
+     */
+    const QUEUE_KEY_PREFIX = 'queue';
+
+    /**
+     * Data HASH sequence key name.
+     */
+    const QUEUE_HKEY_SEQ = 'seq';
+
+    /**
+     * Get data HASH key
+     *
+     * Key will already be prefixed
+     *
+     * @return string
+     */
+    public function getKeyForData()
+    {
+        return $this->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);
+    }
+}

+ 106 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php

@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @todo
+ *   Set high expire value to the hash for rotation when memory is empty
+ *   React upon cache clear all and rebuild path list?
+ */
+class Redis_Queue_PhpRedis extends Redis_Queue_Base
+{
+    public function createItem($data)
+    {
+        $client = $this->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()
+        );
+    }
+}
+

+ 141 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/AbstractUnitTestCase.php

@@ -0,0 +1,141 @@
+<?php
+
+abstract class Redis_Tests_AbstractUnitTestCase extends DrupalUnitTestCase
+{
+    /**
+     * @var boolean
+     */
+    static protected $loaderEnabled = false;
+
+    /**
+     * Enable the autoloader
+     *
+     * This exists in this class in case the autoloader is not set into the
+     * settings.php file or another way
+     *
+     * @return void|boolean
+     */
+    static protected function enableAutoload()
+    {
+        if (self::$loaderEnabled) {
+            return;
+        }
+        if (class_exists('Redis_Client')) {
+            return;
+        }
+
+        spl_autoload_register(function ($className) {
+            $parts = explode('_', $className);
+            if ('Redis' === $parts[0]) {
+                $filename = __DIR__ . '/../lib/' . implode('/', $parts) . '.php';
+                return (bool) include_once $filename;
+            }
+            return false;
+        }, null, true);
+
+        self::$loaderEnabled = true;
+    }
+
+    /**
+     * Drupal $conf array backup
+     *
+     * @var array
+     */
+    private $originalConf = array(
+        'cache_lifetime'          => 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();
+    }
+}

+ 60 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Admin/VariableTestCase.test

@@ -0,0 +1,60 @@
+<?php
+
+class Redis_Tests_Admin_VariableTestCase extends DrupalWebTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'  => '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");
+    }
+}

+ 27 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFixesUnitTestCase.test

@@ -0,0 +1,27 @@
+<?php
+
+if (!class_exists('Redis_Tests_Cache_FixesUnitTestCase')) {
+  require_once(__DIR__ . '/FixesUnitTestCase.php');
+}
+
+class Redis_Tests_Cache_CompressedPhpRedisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 27 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisFlushUnitTestCase.test

@@ -0,0 +1,27 @@
+<?php
+
+if (!class_exists('Redis_Tests_Cache_FlushUnitTestCase')) {
+  require_once(__DIR__ . '/FlushUnitTestCase.php');
+}
+
+class Redis_Tests_Cache_CompressedPhpRedisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 25 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFixesUnitTestCase.test

@@ -0,0 +1,25 @@
+<?php
+
+class Redis_Tests_Cache_CompressedPhpRedisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 25 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedFlushUnitTestCase.test

@@ -0,0 +1,25 @@
+<?php
+
+class Redis_Tests_Cache_CompressedPhpRedisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 25 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/CompressedPhpRedisShardedWithPipelineFixesUnitTestCase.test

@@ -0,0 +1,25 @@
+<?php
+
+class Redis_Tests_Cache_CompressedPhpRedisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 209 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FixesUnitTestCase.php

@@ -0,0 +1,209 @@
+<?php
+
+if (!class_exists('Redis_Tests_AbstractUnitTestCase')) {
+  require_once(__DIR__ . '/../AbstractUnitTestCase.php');
+}
+
+/**
+ * Bugfixes made over time test class.
+ */
+abstract class Redis_Tests_Cache_FixesUnitTestCase extends Redis_Tests_AbstractUnitTestCase
+{
+    /**
+     * @var Cache bin identifier
+     */
+    static private $id = 1;
+
+    protected function createCacheInstance($name = null)
+    {
+        return new Redis_Cache($name);
+    }
+
+    /**
+     * Get cache backend
+     *
+     * @return Redis_Cache
+     */
+    final protected function getBackend($name = null)
+    {
+        if (null === $name) {
+            // This is needed to avoid conflict between tests, each test
+            // seems to use the same Redis namespace and conflicts are
+            // possible.
+            $name = 'cache' . (self::$id++);
+        }
+
+        $backend = $this->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);
+    }
+}

+ 185 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/FlushUnitTestCase.php

@@ -0,0 +1,185 @@
+<?php
+
+abstract class Redis_Tests_Cache_FlushUnitTestCase extends Redis_Tests_AbstractUnitTestCase
+{
+    /**
+     * @var Cache bin identifier
+     */
+    static private $id = 1;
+
+    protected function createCacheInstance($name = null)
+    {
+        return new Redis_Cache($name);
+    }
+
+    /**
+     * Get cache backend
+     *
+     * @return Redis_Cache
+     */
+    final protected function getBackend($name = null)
+    {
+        if (null === $name) {
+            // This is needed to avoid conflict between tests, each test
+            // seems to use the same Redis namespace and conflicts are
+            // possible.
+            $name = 'cache' . (self::$id++);
+        }
+
+        $backend = $this->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));
+        }
+    }
+}

+ 22 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFixesUnitTestCase.test

@@ -0,0 +1,22 @@
+<?php
+
+if (!class_exists('Redis_Tests_Cache_FixesUnitTestCase')) {
+  require_once(__DIR__ . '/FixesUnitTestCase.php');
+}
+
+class Redis_Tests_Cache_PhpRedisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => 'PhpRedis cache fixes',
+            'description' => 'Tests Redis module cache fixes feature.',
+            'group'       => 'Redis',
+        );
+    }
+
+    protected function getClientInterface()
+    {
+        return 'PhpRedis';
+    }
+}

+ 22 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisFlushUnitTestCase.test

@@ -0,0 +1,22 @@
+<?php
+
+if (!class_exists('Redis_Tests_Cache_FlushUnitTestCase')) {
+  require_once(__DIR__ . '/FlushUnitTestCase.php');
+}
+
+class Redis_Tests_Cache_PhpRedisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => 'PhpRedis cache flush',
+            'description' => 'Tests Redis module cache flush modes feature.',
+            'group'       => 'Redis',
+        );
+    }
+
+    protected function getClientInterface()
+    {
+        return 'PhpRedis';
+    }
+}

+ 20 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFixesUnitTestCase.test

@@ -0,0 +1,20 @@
+<?php
+
+class Redis_Tests_Cache_PhpRedisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 20 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedFlushUnitTestCase.test

@@ -0,0 +1,20 @@
+<?php
+
+class Redis_Tests_Cache_PhpRedisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 20 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PhpRedisShardedWithPipelineFixesUnitTestCase.test

@@ -0,0 +1,20 @@
+<?php
+
+class Redis_Tests_Cache_PhpRedisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 18 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFixesUnitTestCase.test

@@ -0,0 +1,18 @@
+<?php
+
+class Redis_Tests_Cache_PredisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'         => 'Predis cache fixes',
+            'description'  => 'Tests Redis module cache fixes feature.',
+            'group'        => 'Redis',
+        );
+    }
+
+    protected function getClientInterface()
+    {
+        return 'Predis';
+    }
+}

+ 18 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisFlushUnitTestCase.test

@@ -0,0 +1,18 @@
+<?php
+
+class Redis_Tests_Cache_PredisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'         => 'Predis cache flush',
+            'description'  => 'Tests Redis module cache flush modes feature.',
+            'group'        => 'Redis',
+        );
+    }
+
+    protected function getClientInterface()
+    {
+        return 'Predis';
+    }
+}

+ 20 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFixesUnitTestCase.test

@@ -0,0 +1,20 @@
+<?php
+
+class Redis_Tests_Cache_PredisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'         => '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';
+    }
+}

+ 20 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedFlushUnitTestCase.test

@@ -0,0 +1,20 @@
+<?php
+
+class Redis_Tests_Cache_PredisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'         => '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';
+    }
+}

+ 20 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Cache/PredisShardedWithPipelineFixesUnitTestCase.test

@@ -0,0 +1,20 @@
+<?php
+
+class Redis_Tests_Cache_PredisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'         => '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';
+    }
+}

+ 66 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/ClientUnitTestCase.test

@@ -0,0 +1,66 @@
+<?php
+
+class Redis_Tests_Client_UnitTestCase extends Redis_Tests_AbstractUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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);
+        }
+    }
+}

+ 14 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Client/MockFactory.php

@@ -0,0 +1,14 @@
+<?php
+
+class Redis_Tests_Client_MockFactory implements Redis_Client_FactoryInterface
+{
+    public function getClient($options = array())
+    {
+        return (object)$options;
+    }
+
+    public function getName()
+    {
+        return 'Mock';
+    }
+}

+ 119 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/LockingUnitTestCase.php

@@ -0,0 +1,119 @@
+<?php
+
+abstract class Redis_Tests_Lock_LockingUnitTestCase extends Redis_Tests_AbstractUnitTestCase
+{
+    /**
+     * Ensure lock flush at tear down
+     *
+     * @var array
+     */
+    protected $backends = array();
+
+    /**
+     * Get the lock client class name
+     *
+     * @return string
+     *   Lock backend class name or null if cannot spawn it
+     */
+    abstract protected function getLockBackendClass();
+
+    public function tearDown()
+    {
+        if (!empty($this->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");
+         */
+    }
+}

+ 27 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PhpRedisLockingUnitTestCase.test

@@ -0,0 +1,27 @@
+<?php
+
+if (!class_exists('Redis_Tests_Lock_LockingUnitTestCase')) {
+  require_once(__DIR__ . '/LockingUnitTestCase.php');
+}
+
+class Redis_Tests_Lock_PhpRedisLockingUnitTestCase extends Redis_Tests_Lock_LockingUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 23 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Lock/PredisLockingUnitTestCase.test

@@ -0,0 +1,23 @@
+<?php
+
+class Redis_Tests_Lock_PredisLockingUnitTestCase extends Redis_Tests_Lock_LockingUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => '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';
+    }
+}

+ 148 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PathUnitTestCase.php

@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * Bugfixes made over time test class.
+ */
+abstract class Redis_Tests_Path_PathUnitTestCase extends Redis_Tests_AbstractUnitTestCase
+{
+    /**
+     * @var Cache bin identifier
+     */
+    static private $id = 1;
+
+    /**
+     * Get cache backend
+     *
+     * @return Redis_Path_HashLookupInterface
+     */
+    final protected function getBackend($name = null)
+    {
+        if (null === $name) {
+            // This is needed to avoid conflict between tests, each test
+            // seems to use the same Redis namespace and conflicts are
+            // possible.
+            $name = 'cache' . (self::$id++);
+        }
+
+        $className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_PATH);
+        $hashLookup = new $className(Redis_Client::getClient(), 'path', Redis_Client::getDefaultPrefix('path'));
+
+        return $hashLookup;
+    }
+
+    /**
+     * Tests basic functionnality
+     */
+    public function testPathLookup()
+    {
+        $backend = $this->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);
+    }
+}

+ 22 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PhpRedisPathUnitTestCase.test

@@ -0,0 +1,22 @@
+<?php
+
+if (!class_exists('Redis_Tests_Path_PathUnitTestCase')) {
+  require_once(__DIR__ . '/PathUnitTestCase.php');
+}
+
+class Redis_Tests_Path_PhpRedisPathUnitTestCase extends Redis_Tests_Path_PathUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => 'PhpRedis path inc replacement',
+            'description' => 'Tests PhpRedis path inc replacement.',
+            'group'       => 'Redis',
+        );
+    }
+
+    protected function getClientInterface()
+    {
+        return 'PhpRedis';
+    }
+}

+ 18 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Path/PredisPathUnitTestCase.test

@@ -0,0 +1,18 @@
+<?php
+
+class Redis_Tests_Path_PredisPathUnitTestCase extends Redis_Tests_Path_PathUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => 'Predis path inc replacement',
+            'description' => 'Tests Predis path inc replacement.',
+            'group'       => 'Redis',
+        );
+    }
+
+    protected function getClientInterface()
+    {
+        return 'Predis';
+    }
+}

+ 22 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PhpRedisQueueUnitTestCase.test

@@ -0,0 +1,22 @@
+<?php
+
+if (!class_exists('Redis_Tests_Queue_QueueUnitTestCase')) {
+  require_once(__DIR__ . '/QueueUnitTestCase.php');
+}
+
+class Redis_Tests_Queue_PhpRedisQueueUnitTestCase extends Redis_Tests_Queue_QueueUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => 'PhpRedis Redis queue',
+            'description' => 'Ensure that Redis queue feature is working OK.',
+            'group'       => 'Redis',
+        );
+    }
+
+    protected function getClientInterface()
+    {
+        return 'PhpRedis';
+    }
+}

+ 20 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/PredisQueueUnitTestCase.test

@@ -0,0 +1,20 @@
+<?php
+
+/*
+class Redis_Tests_Queue_PredisQueueUnitTestCase extends Redis_Tests_Queue_QueueUnitTestCase
+{
+    public static function getInfo()
+    {
+        return array(
+            'name'        => 'Predis Redis queue',
+            'description' => 'Ensure that Redis queue feature is working OK.',
+            'group'       => 'Redis',
+        );
+    }
+
+    protected function getClientInterface()
+    {
+        return 'Predis';
+    }
+}
+ */

+ 133 - 0
sites/all/modules/contrib/dev/redis/lib/Redis/Tests/Queue/QueueUnitTestCase.php

@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * Some tests in there credits goes to the redis_queue module.
+ * Thanks to their author.
+ */
+abstract class Redis_Tests_Queue_QueueUnitTestCase extends Redis_Tests_AbstractUnitTestCase
+{
+    /**
+     * @var Redis_Queue
+     */
+    public $queue;
+
+    /**
+     * @var string
+     */
+    public $name;
+
+    public function setUp()
+    {
+        parent::setUp();
+
+        module_load_include('inc', 'system', 'system.queue');
+
+        $this->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();
+    }
+     */
+}

+ 104 - 0
sites/all/modules/contrib/dev/redis/redis.admin.inc

@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Redis module administration pages.
+ */
+
+/**
+ * Main settings and review administration screen.
+ */
+function redis_settings_form($form, &$form_state) {
+
+  $form['connection'] = array(
+    '#type' => '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 <em>tcp</em> 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 <em>@default</em>.", 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 <em>@default</em>.", 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];
+    }
+  }
+}

+ 25 - 0
sites/all/modules/contrib/dev/redis/redis.autoload.inc

@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Redis module autoloader.
+ */
+
+/**
+ * Autoloader micro optimization, work with constant as much as we can.
+ */
+define('REDIS_ROOT', dirname(__FILE__) . '/lib');
+
+/**
+ * Redis module specific autoloader, compatible with spl_register_autoload().
+ */
+function redis_autoload($class_name) {
+  if ('Redis' === substr($class_name, 0, 5)) {
+    $filename = REDIS_ROOT . '/' . str_replace('_', '/', $class_name) . '.php';
+    return @include_once $filename;
+  }
+  return FALSE;
+}
+
+// Register our custom autoloader.
+spl_autoload_register('redis_autoload');

+ 44 - 0
sites/all/modules/contrib/dev/redis/redis.info

@@ -0,0 +1,44 @@
+name = "Redis"
+description = "Provide a module placeholder, for using as dependency for module that needs Redis."
+package = Performance
+version = VERSION
+core = 7.x
+configure = admin/config/development/performance/redis
+; Drupal Simpletest cannot rely on a real autoloader
+files[] = lib/Redis/Tests/AbstractUnitTestCase.php
+files[] = lib/Redis/Tests/Admin/VariableTestCase.test
+files[] = lib/Redis/Tests/Cache/FixesUnitTestCase.php
+files[] = lib/Redis/Tests/Cache/FlushUnitTestCase.php
+files[] = lib/Redis/Tests/Cache/CompressedPhpRedisFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/CompressedPhpRedisFlushUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/CompressedPhpRedisShardedFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/CompressedPhpRedisShardedFlushUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/CompressedPhpRedisShardedWithPipelineFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PhpRedisFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PhpRedisFlushUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PhpRedisShardedFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PhpRedisShardedFlushUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PhpRedisShardedWithPipelineFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PredisFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PredisFlushUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PredisShardedFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PredisShardedFlushUnitTestCase.test
+files[] = lib/Redis/Tests/Cache/PredisShardedWithPipelineFixesUnitTestCase.test
+files[] = lib/Redis/Tests/Client/ClientUnitTestCase.test
+files[] = lib/Redis/Tests/Client/MockFactory.php
+files[] = lib/Redis/Tests/Lock/LockingUnitTestCase.php
+files[] = lib/Redis/Tests/Lock/PhpRedisLockingUnitTestCase.test
+files[] = lib/Redis/Tests/Lock/PredisLockingUnitTestCase.test
+files[] = lib/Redis/Tests/Path/PathUnitTestCase.php
+files[] = lib/Redis/Tests/Path/PhpRedisPathUnitTestCase.test
+files[] = lib/Redis/Tests/Path/PredisPathUnitTestCase.test
+files[] = lib/Redis/Tests/Queue/QueueUnitTestCase.php
+files[] = lib/Redis/Tests/Queue/PhpRedisQueueUnitTestCase.test
+files[] = lib/Redis/Tests/Queue/PredisQueueUnitTestCase.test
+
+; Information added by Drupal.org packaging script on 2017-12-22
+version = "7.x-3.17"
+core = "7.x"
+project = "redis"
+datestamp = "1513939095"
+

+ 38 - 0
sites/all/modules/contrib/dev/redis/redis.install

@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Redis install related functions.
+ */
+
+/**
+ * Implements hook_requirements().
+ */
+function redis_requirements($phase) {
+
+  // This module is configured via settings.php file. Using any other phase
+  // than runtime to proceed to some consistency checks is useless.
+  if ('runtime' !== $phase) {
+    return array();
+  }
+
+  $requirements = array();
+
+  try {
+    Redis_Client::getClient();
+    $requirements['redis'] = array(
+      'title'       => "Redis",
+      'value'       => t("Connected, using the <em>@name</em> 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;
+}

+ 63 - 0
sites/all/modules/contrib/dev/redis/redis.lock.inc

@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Drupal core lock.inc replacement.
+ * 
+ * Do not use this file directly, it will be included by the backend specific
+ * implementation when added to settings.php file.
+ * 
+ * See README.txt file for details.
+ */
+
+// Include our own autoloader to ensure classes to be there.
+// We cannot rely on core in case of early bootstrap phases.
+require_once dirname(__FILE__) . '/redis.autoload.inc';
+
+/**
+ * Foo function, keeping it for API consistency (Drupal 7).
+ */
+function lock_initialize() {}
+
+/**
+ * Foo function, keeping it for API consistency (Drupal 6).
+ */
+function lock_init() {}
+
+/**
+ * Foo function, keeping it for API consistency.
+ * Some insane people may actually use it.
+ */
+function _lock_id() {
+  return Redis_Lock::getBackend()->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);
+  }
+}

+ 73 - 0
sites/all/modules/contrib/dev/redis/redis.module

@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Redis module.
+ *
+ * This file is a placeholder for other modules that need the Redis client for
+ * something else than lock and cache.
+ */
+
+// Include our own autoloader to ensure classes to be there.
+// We cannot rely on core in case of early bootstrap phases.
+require_once dirname(__FILE__) . '/redis.autoload.inc';
+
+/**
+ * Implements hook_menu().
+ */
+function redis_menu() {
+  $items = array();
+  $items['admin/config/development/performance/cache'] = array(
+    'title' => "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 =
+        '<p>' . 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 <em>settings.php</em> 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.") . '</p>' .
+        '<p>' . 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 <em>settings.php</em> 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.") . '</p>';
+      if (Redis_Client::getClient()) {
+        $messages .= '<p><strong>' . t("Current connected client uses the <em>@name</em> library.", array('@name' => Redis_Client::getClientInterfaceName())) . '</strong></p>';
+      }
+      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();
+}

+ 594 - 0
sites/all/modules/contrib/dev/redis/redis.path.inc

@@ -0,0 +1,594 @@
+<?php
+
+/**
+ * @file
+ * Drupal default includes/path.inc file copy which only differs in:
+ *  - drupal_lookup_path() which is the only performance critical.
+ *  - path_*() functions for synchronization.
+ */
+
+/**
+ * Get Redis path lookup backend.
+ *
+ * @return Redis_Path_HashLookupInterface
+ */
+function redis_path_backend_get() {
+  $hashLookup = &drupal_static(__FUNCTION__, null);
+
+  if (null === $hashLookup) {
+    try {
+      $className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_PATH);
+      $hashLookup = new $className(Redis_Client::getClient(), 'path', Redis_Client::getDefaultPrefix('path'));
+    } catch (Exception $e) {
+      $hashLookup = new Redis_Path_NullHashLookup();
+    }
+  }
+
+  return $hashLookup;
+}
+
+/**
+ * Initialize the $_GET['q'] variable to the proper normal path.
+ */
+function drupal_path_initialize() {
+  // Ensure $_GET['q'] is set before calling drupal_normal_path(), to support
+  // path caching with hook_url_inbound_alter().
+  if (empty($_GET['q'])) {
+    $_GET['q'] = variable_get('site_frontpage', 'node');
+  }
+  $_GET['q'] = drupal_get_normal_path($_GET['q']);
+}
+
+/**
+ * Given an alias, return its Drupal system URL if one exists. Given a Drupal
+ * system URL return one of its aliases if such a one exists. Otherwise,
+ * return FALSE.
+ *
+ * @param $action
+ *   One of the following values:
+ *   - wipe: delete the alias cache.
+ *   - alias: return an alias for a given Drupal system path (if one exists).
+ *   - source: return the Drupal system URL for a path alias (if one exists).
+ * @param $path
+ *   The path to investigate for corresponding aliases or system URLs.
+ * @param $path_language
+ *   Optional language code to search the path with. Defaults to the page language.
+ *   If there's no path defined for that language it will search paths without
+ *   language.
+ *
+ * @return
+ *   Either a Drupal system path, an aliased path, or FALSE if no path was
+ *   found.
+ */
+function drupal_lookup_path($action, $path = '', $path_language = NULL) {
+  global $language_url;
+
+  static $cache, $denyAdmin;
+
+  if (null === $cache) {
+    $cache = array('whitelist' => 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 <front> with the frontpage.
+    $to_replace = array(
+      '/(\r\n?|\n)/', // newlines
+      '/\\\\\*/',     // asterisks
+      '/(^|\|)\\\\<front\\\\>($|\|)/' // <front>
+    );
+    $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 == '<front>' || 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);
+}